JAXB: How should I marshal complex nested data structures? JAXB: How should I marshal complex nested data structures? xml xml

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;            }    }}