JAXB: How should I marshal complex nested data structures?
I've solved the problem without XmlAdapter's.
I've written JAXB-annotated objects for Map, Map.Entry and Collection.
The main idea is inside the method xmlizeNestedStructure(...):
Take a look at the code:
public final class Adapters {private Adapters() {}public static Class<?>[] getXmlClasses() { return new Class<?>[]{ XMap.class, XEntry.class, XCollection.class, XCount.class };}public static Object xmlizeNestedStructure(Object input) { if (input instanceof Map<?, ?>) { return xmlizeNestedMap((Map<?, ?>) input); } if (input instanceof Collection<?>) { return xmlizeNestedCollection((Collection<?>) input); } return input; // non-special object, return as is}public static XMap<?, ?> xmlizeNestedMap(Map<?, ?> input) { XMap<Object, Object> ret = new XMap<Object, Object>(); for (Map.Entry<?, ?> e : input.entrySet()) { ret.add(xmlizeNestedStructure(e.getKey()), xmlizeNestedStructure(e.getValue())); } return ret;}public static XCollection<?> xmlizeNestedCollection(Collection<?> input) { XCollection<Object> ret = new XCollection<Object>(); for (Object entry : input) { ret.add(xmlizeNestedStructure(entry)); } return ret;}@XmlType@XmlRootElementpublic final static class XMap<K, V> { @XmlElementWrapper(name = "map") @XmlElement(name = "entry") private List<XEntry<K, V>> list = new LinkedList<XEntry<K, V>>(); public XMap() { } public void add(K key, V value) { list.add(new XEntry<K, V>(key, value)); }}@XmlType@XmlRootElementpublic final static class XEntry<K, V> { @XmlElement private K key; @XmlElement private V value; private XEntry() { } public XEntry(K key, V value) { this.key = key; this.value = value; }}@XmlType@XmlRootElementpublic final static class XCollection<V> { @XmlElementWrapper(name = "list") @XmlElement(name = "entry") private List<V> list = new LinkedList<V>(); public XCollection() { } public void add(V obj) { list.add(obj); }}}
It works!
Let's look at a demo output:
<xMap> <map> <entry> <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <count>1</count> <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a</content> </key> <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <list> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a1</entry> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a2</entry> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a3</entry> </list> </value> </entry> <entry> <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <count>2</count> <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b</content> </key> <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <list> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b1</entry> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b3</entry> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b2</entry> </list> </value> </entry> <entry> <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <count>3</count> <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c</content> </key> <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <list> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c1</entry> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c2</entry> <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c3</entry> </list> </value> </entry> </map></xMap>
Sorry, the demo output uses also a data structure called "count"which is not mentioned in the Adapter's source code.
BTW: does anyone know how to remove all these annoyingand (in my case) unnecessary xsi:type attributes?
I had the same requirement to use a Map< String,Map< String,Integer>>. I used the XMLAdapter and it worked fine. Using XMLAdaptor is the cleanest solution I think. Below is the code of the adaptor.This is the jaXb class code snippet.
@XmlJavaTypeAdapter(MapAdapter.class) Map<String, Map<String, Integer>> mapOfMap = new HashMap<String,Map<String, Integer>>();
MapType Class :
public class MapType {public List<MapEntryType> host = new ArrayList<MapEntryType>();}
MapEntry Type Class:
public class MapEntryType {@XmlAttributepublic String ip;@XmlElementpublic List<LinkCountMapType> request_limit = new ArrayList<LinkCountMapType>();}
LinkCountMapType Class:
public class LinkCountMapType {@XmlAttributepublic String service;@XmlValuepublic Integer count;}
Finally the MapAdaptor Class:
public final class MapAdapter extends XmlAdapter<MapType, Map<String, Map<String, Integer>>> {@Overridepublic Map<String, Map<String, Integer>> unmarshal(MapType v) throws Exception { Map<String, Map<String, Integer>> mainMap = new HashMap<String, Map<String, Integer>>(); List<MapEntryType> myMapEntryTypes = v.host; for (MapEntryType myMapEntryType : myMapEntryTypes) { Map<String, Integer> linkCountMap = new HashMap<String, Integer>(); for (LinkCountMapType myLinkCountMapType : myMapEntryType.request_limit) { linkCountMap.put(myLinkCountMapType.service, myLinkCountMapType.count); } mainMap.put(myMapEntryType.ip, linkCountMap); } return mainMap;}@Overridepublic MapType marshal(Map<String, Map<String, Integer>> v) throws Exception { MapType myMapType = new MapType(); List<MapEntryType> entry = new ArrayList<MapEntryType>(); for (String ip : v.keySet()) { MapEntryType myMapEntryType = new MapEntryType(); Map<String, Integer> linkCountMap = v.get(ip); List<LinkCountMapType> linkCountList = new ArrayList<LinkCountMapType>(); for (String link : linkCountMap.keySet()) { LinkCountMapType myLinkCountMapType = new LinkCountMapType(); Integer count = linkCountMap.get(link); myLinkCountMapType.count = count; myLinkCountMapType.service = link; linkCountList.add(myLinkCountMapType); } myMapEntryType.ip = ip; myMapEntryType.request_limit = linkCountList; entry.add(myMapEntryType); } myMapType.host = entry; return myMapType;}
}
Marshalling a Jaxb Object will give the below XML
<mapOfmap> <host ip="127.0.0.1"> <request_limit service="service1">7</request_limit> <request_limit service="service2">8</request_limit> </host></mapOfmap>
Following is the code with "dexmlize" ability based on above Ivan's code. Usage:
Map<String, List> nameMapResult = (Map<String, List>) Adapters.dexmlizeNestedStructure(unmarshallResult);
In order to restore the collection and map class, a new field will be xmlized to record the class information. Detailed code:
class Adapters { private Adapters() { } public static Class<?>[] getXmlClasses() { return new Class<?>[]{XMap.class, XEntry.class, XCollection.class}; } public static Object xmlizeNestedStructure(Object input) { if (input instanceof Map<?, ?>) { return xmlizeNestedMap((Map<?, ?>) input); } if (input instanceof Collection<?>) { return xmlizeNestedCollection((Collection<?>) input); } return input; // non-special object, return as is } public static Object dexmlizeNestedStructure(Object input) { if (input instanceof XMap<?, ?>) { return dexmlizeNestedMap((XMap<?, ?>) input); } if (input instanceof XCollection<?>) { return dexmlizeNestedCollection((XCollection<?>) input); } return input; // non-special object, return as is } private static Object dexmlizeNestedCollection(XCollection<?> input) { Class<? extends Collection> clazz = input.getClazz(); Collection collection = null; try { collection = clazz.newInstance(); List dataList = input.getList(); for (Object object : dataList) { collection.add(dexmlizeNestedStructure(object)); } } catch (Exception e) { e.printStackTrace(); } return collection; } private static Object dexmlizeNestedMap(XMap<?, ?> input) { Class<? extends Map> clazz = input.getClazz(); Map map = null; try { map = clazz.newInstance(); List<? extends XEntry> entryList = input.getList(); for (XEntry xEntry : entryList) { Object key = dexmlizeNestedStructure(xEntry.getKey()); Object value = dexmlizeNestedStructure(xEntry.getValue()); map.put(key, value); } } catch (Exception e) { e.printStackTrace(); } return map; } public static XMap<?, ?> xmlizeNestedMap(Map<?, ?> input) { XMap<Object, Object> ret = new XMap<Object, Object>(input.getClass()); for (Map.Entry<?, ?> e : input.entrySet()) { ret.add(xmlizeNestedStructure(e.getKey()), xmlizeNestedStructure(e.getValue())); } return ret; } public static XCollection<?> xmlizeNestedCollection(Collection<?> input) { XCollection<Object> ret = new XCollection<Object>(input.getClass()); for (Object entry : input) { ret.add(xmlizeNestedStructure(entry)); } return ret; } @XmlType @XmlRootElement public final static class XMap<K, V>{ private List<XEntry<K, V>> list = new ArrayList<XEntry<K, V>>(); private Class<? extends Map> clazz = null; public XMap(Class mapClazz) { this.clazz = (Class<? extends Map>)mapClazz; } public XMap() { } public void add(K key, V value) { list.add(new XEntry<K, V>(key, value)); } @XmlElementWrapper(name = "map") @XmlElement(name = "entry") public List<XEntry<K, V>> getList() { return list; } public void setList(List<XEntry<K, V>> list) { this.list = list; } @XmlElement(name="clazz") public Class<? extends Map> getClazz() { return clazz; } public void setClazz(Class<? extends Map> clazz) { this.clazz = clazz; } } @XmlType @XmlRootElement public final static class XEntry<K, V> { private K key; private V value; private XEntry() { } public XEntry(K key, V value) { this.key = key; this.value = value; } @XmlElement public K getKey() { return key; } public void setKey(K key) { this.key = key; } @XmlElement public V getValue() { return value; } public void setValue(V value) { this.value = value; } } @XmlType @XmlRootElement public final static class XCollection<V> { private List<V> list = new ArrayList<V>(); private Class<? extends Collection> clazz = null; public XCollection(Class collectionClazz) { this.clazz = collectionClazz; } public XCollection() { } public void add(V obj) { list.add(obj); } @XmlElementWrapper(name = "collection") @XmlElement(name = "entry") public List<V> getList() { return list; } public void setList(List<V> list) { this.list = list; } @XmlElement(name="clazz") public Class<? extends Collection> getClazz() { return clazz; } public void setClazz(Class<? extends Collection> clazz) { this.clazz = clazz; } }}