Jaxb: How to Marshall Map into <Key>Value</Key>

JAXB: how to marshall map into key value /key

the code provided didn't work for me.
I found another way to Map :

MapElements :

package com.cellfish.mediadb.rest.lucene;

import javax.xml.bind.annotation.XmlElement;

class MapElements
{
@XmlElement public String key;
@XmlElement public Integer value;

private MapElements() {} //Required by JAXB

public MapElements(String key, Integer value)
{
this.key = key;
this.value = value;
}
}

MapAdapter :

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.annotation.adapters.XmlAdapter;

class MapAdapter extends XmlAdapter<MapElements[], Map<String, Integer>> {
public MapElements[] marshal(Map<String, Integer> arg0) throws Exception {
MapElements[] mapElements = new MapElements[arg0.size()];
int i = 0;
for (Map.Entry<String, Integer> entry : arg0.entrySet())
mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());

return mapElements;
}

public Map<String, Integer> unmarshal(MapElements[] arg0) throws Exception {
Map<String, Integer> r = new HashMap<String, Integer>();
for (MapElements mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}

The rootElement :

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Root {

private Map<String, Integer> mapProperty;

public Root() {
mapProperty = new HashMap<String, Integer>();
}

@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, Integer> getMapProperty() {
return mapProperty;
}

public void setMapProperty(Map<String, Integer> map) {
this.mapProperty = map;
}

}

I found the code in this website :
http://www.developpez.net/forums/d972324/java/general-java/xml/hashmap-jaxb/

JAXB Marshal and Unmarshal Map to/from key value /key

A very small correction:

map.put(element.getNodeName(), element.getTextContent());

JAXB java.util.Map to key value pairs

Like I hinted in my comment, this cannot be achieved with JAXB alone. In the JAXB specification (JSR 222) it says:

In all application scenarios, we create a Java object-level binding of the schema.

That means that the scope of the binding is the same as the scope of the schema, which is static. A JAXB binding is not meant to be changed without recompiling the code. There are some exceptions, e.g. for xs:anyAttribute which is discussed in section 6.9 of the specification, but since you didn't vote for the answer suggesting the use of @XmlAnyAttribute you probably don't want to live with the limitations - e.g. only have QName keys in your map.

I hope you are convinced that to do what you want with JAXB is a really bad idea, but just for reference below is an example that modifies the document after marshalling to bring it to the structure you want. You can copy and paste it into a single file and compile it with Java 7. The output will look like this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<mapExample>
<map France="Paris" Japan="Tokyo"/>
</mapExample>

My code only shows the marshalilng the other direction is equivalent:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

@XmlRootElement
class MapExample {
@XmlJavaTypeAdapter(MapXmlAdapter.class)
@XmlElement(name="map")
private Map<String, String> data = new HashMap<>();

public static void main(String[] args) throws Exception {
MapExample example = new MapExample();
example.data.put("France", "Paris");
example.data.put("Japan", "Tokyo");

JAXBContext context = JAXBContext.newInstance(MapExample.class);
Marshaller marshaller = context.createMarshaller();
DOMResult result = new DOMResult();
marshaller.marshal(example, result);

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();

Document document = (Document)result.getNode();
XPathExpression expression = xpath.compile("//map/entry");
NodeList nodes = (NodeList)expression.evaluate(document, XPathConstants.NODESET);

expression = xpath.compile("//map");
Node oldMap = (Node)expression.evaluate(document, XPathConstants.NODE);
Element newMap = document.createElement("map");

for (int index = 0; index < nodes.getLength(); index++) {
Element element = (Element)nodes.item(index);
newMap.setAttribute(element.getAttribute("key"),
element.getAttribute("value"));
}

expression = xpath.compile("//map/..");
Node parent = (Node)expression.evaluate(document, XPathConstants.NODE);
parent.replaceChild(newMap, oldMap);

TransformerFactory.newInstance().newTransformer().
transform(new DOMSource(document), new StreamResult(System.out));
}
}

class MapXmlAdapter extends XmlAdapter<MyMap, Map<String, String>> {
@Override
public Map<String, String> unmarshal(MyMap value) throws Exception {
throw new UnsupportedOperationException();
}

@Override
public MyMap marshal(Map<String, String> value) throws Exception {
MyMap map = new MyMap();
map.entries = new ArrayList<MyEntry>();
for (String key : value.keySet()) {
MyEntry entry = new MyEntry();
entry.key = key;
entry.value = value.get(key);
map.entries.add(entry);
}
return map;
}
}

class MyMap {
@XmlElement(name="entry")
public List<MyEntry> entries;
}

class MyEntry {
@XmlAttribute
public String key;

@XmlAttribute
public String value;
}

Using JAXB to create XML from MAP

I did it! used this Example

From that above post: Created this class:

    public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, String>>{

@Override
public Map<String, String> unmarshal(MapWrapper v) throws Exception {
Map<String, String> map = new HashMap<String,String>();//v.toMap();

return map;
}

@Override
public MapWrapper marshal(Map<String, String> m) throws Exception {
MapWrapper wrapper = new MapWrapper();

for(Map.Entry<String, String> entry : m.entrySet()){
wrapper.addEntry(new JAXBElement<String>(new QName(entry.getKey()), String.class, entry.getValue()));
}

return wrapper;
}

}

MapWrapper Class:

@XmlType
public class MapWrapper{
private List<JAXBElement<String>> properties = new ArrayList<>();

public MapWrapper(){

}

@XmlAnyElement
public List<JAXBElement<String>> getProperties() {
return properties;
}
public void setProperties(List<JAXBElement<String>> properties) {
this.properties = properties;
}
public void addEntry(JAXBElement<String> prop){
properties.add(prop);
}

public void addEntry(String key, String value){
JAXBElement<String> prop = new JAXBElement<String>(new QName(key), String.class, value);
addEntry(prop);
}

}

Created This CustomMap

@XmlRootElement(name="RootTag")
public class CustomMap extends MapWrapper{
public CustomMap(){

}
}

Test Code by creating XML:

private static void writeAsXml(Object o, Writer writer) throws Exception
{
JAXBContext jaxb = JAXBContext.newInstance(o.getClass());

Marshaller xmlConverter = jaxb.createMarshaller();
xmlConverter.setProperty("jaxb.formatted.output", true);
xmlConverter.marshal(o, writer);
}

CustomMap map = new CustomMap();
map.addEntry("Key1","Value1");
map.addEntry("Key2","Value2");
map.addEntry("Key3","Value3");
map.addEntry("Key4","Value4");
writeAsXml(map, new PrintWriter(System.out));

And produced XML:

<RootTag>
<Key1>Value1</Key1>
<Key2>Value2</Key2>
<Key3>Value3</Key3>
<Key4>Value4</Key4>
</RootTag>

I needed only Marshaling So did not implement that Unmarshal part.

How to marshall Map String, List Objects using JAXB

Maps containing collections as values are a bit tricky; you need an adapter and classes for representing the map entries in a way JAXB can handle.

@XmlAccessorType(XmlAccessType.FIELD) 
public class ListOfEntry {
@XmlElement
private List<Entry> list = new ArrayList<>();
public List<Entry> getList(){ return list; }
}

@XmlAccessorType(XmlAccessType.FIELD)
public class Entry {
@XmlElement
private String key;
@XmlElement
private List<Parameter> list = new ArrayList<>();
public String getKey(){ return key; }
public void setKey( String value ){ key = value; }
public List<Parameter> getList(){ return list; }
}

The Adapter does the transformations back and forth:

public class Adapter 
extends XmlAdapter<ListOfEntry, Map<String, List<Parameter>>> {
@Override
public Map<String, List<Parameter>> unmarshal(ListOfEntry loe)
throws Exception {
Map<String, List<Parameter>> map = new HashMap<>();
for(Entry entry : loe.getList() ) {
map.put(entry.getKey(), entry.getList() );
}
return map;
}

@Override
public ListOfEntry marshal(Map<String, List<Parameter>> map)
throws Exception {
ListOfEntry loe = new ListOfEntry();
for(Map.Entry<String, List<Parameter>> mapEntry : map.entrySet()) {
Entry entry = new Entry();
entry.setKey( mapEntry.getKey() );
entry.getList().addAll( mapEntry.getValue() );
loe.getList().add(entry);
}
return loe;
}
}

And you must add an annotation to the Map field:

@XmlElement
@XmlJavaTypeAdapter(Adapter.class)
private Map<String, List<Parameter>> parameterMap = new HashMap<>();

JAXB unmarshal elements of XML to Map

You could do the following:

XmlAdapter (MapAdapter)

You could do the following for your XmlAdapter where you convert an instance of Map to an object that has a List of DOM Element. You will construct the instances of Element so that the name is they key from the map entry, and the text content is the value.

import java.util.*;
import java.util.Map.Entry;

import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.*;

public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, String>> {

private DocumentBuilder documentBuilder;

public MapAdapter() throws Exception {
documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
}

public static class AdaptedMap {
@XmlAnyElement
public List<Element> elements = new ArrayList<Element>();
}

@Override
public AdaptedMap marshal(Map<String, String> map) throws Exception {
Document document = documentBuilder.newDocument();
AdaptedMap adaptedMap = new AdaptedMap();
for(Entry<String, String> entry : map.entrySet()) {
Element element = document.createElement(entry.getKey());
element.setTextContent(entry.getValue());
adaptedMap.elements.add(element);
}
return adaptedMap;
}

@Override
public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
HashMap<String, String> map = new HashMap<String, String>();
for(Element element : adaptedMap.elements) {
map.put(element.getLocalName(), element.getTextContent());
}
return map;
}

}

Optimizing the Use of MapAdapter

To improve performance we want to minimize the number of times the DocumentBuiderFactory and DocumentBuilder is instantiated. We can do this by creating an instance of the MapAdapter for JAXB to use and set it on the Marshaller and Unmarshaller. This way JAXB will use that instance instead of creating a new one each time the adapter is required.

import java.io.File;
import javax.xml.bind.*;

public class Demo {

public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(SMSDetail.class);

MapAdapter mapAdapter = new MapAdapter();

Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setAdapter(mapAdapter);
File xml = new File("src/forum27182975/input.xml");
SMSDetail smsDetail = (SMSDetail) unmarshaller.unmarshal(xml);

Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(mapAdapter);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(smsDetail, System.out);
}

}

If You are Using MOXy as your JAXB (JSR-222) Provider

If you are using MOXy as your JAXB provider then you can leverage the @XmlVariableNode extension to make the mapping of this use case easier:

  • http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-using-maps-key-as.html

How to marshal Map into {key: value, key: value, ...} with MOXy

On a side note, I'm also not able to use the new XmlVariableNode in
Eclipselink 2.6 as I'm only allowed to use stable releases of the API
:(

@XmlVariableNode has also been included in EclipseLink 2.5.1 which is now released:

  • http://www.eclipse.org/eclipselink/downloads/

This annotation is well suited for mapping your use case:

  • http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-using-maps-key-as.html

JaxB How to marshall the content of a list as individual tags

My Advice

My advice is don't do it this way. Instead have your XML message be the following (it will make it easier for everyone to process your XML):

<items>
<item>apple</item>
<item>banana</item>
<item>orange</item>
</items>

How You Could Do It

OK, you have decided not to follow my advice :). Here is how you can do it:

  1. Create an XmlAdapter that can convert a String to an instance of org.w3c.dom.Element with its name equal to the String.

    import javax.xml.bind.annotation.adapters.XmlAdapter;
    import javax.xml.parsers.*;
    import org.w3c.dom.*;

    public class StringAdapter extends XmlAdapter<Object, String> {

    private Document document;

    @Override
    public String unmarshal(Object v) throws Exception {
    Element element = (Element) v;
    return element.getTagName();
    }

    @Override
    public Object marshal(String v) throws Exception {
    return getDocument().createElement(v);
    }

    private Document getDocument() throws Exception {
    if(null == document) {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();
    document = db.newDocument();
    }
    return document;
    }

    }
  2. Annotate your List<String> field/property with @XmlJavaTypeAdapter pointing to your XmlAdapter and the @XmlAnyElement annotation.

     import java.util.*;
    import javax.xml.bind.annotation.*;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

    @XmlRootElement
    public class Items {

    private List<String> items = new ArrayList<String>();

    @XmlAnyElement
    @XmlJavaTypeAdapter(StringAdapter.class)
    public List<String> getItems() {
    return items;
    }

    }
  3. To improve performance, make sure your XmlAdapter holds an instance of Document and set the XMLAdapter on the Marshaller to make it stateful to avoid the need to recreate it each time the XmlAdapter is called.

    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.Marshaller;

    public class Demo {

    public static void main(String[] args) throws Exception {
    JAXBContext jc = JAXBContext.newInstance(Items.class);

    Items items = new Items();
    items.getItems().add("apple");
    items.getItems().add("banana");
    items.getItems().add("orange");

    Marshaller marshaller = jc.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.setAdapter(new StringAdapter());
    marshaller.marshal(items, System.out);
    }

    }


Related Topics



Leave a reply



Submit