JAXB: How should I marshal complex nested data structures?

后端 未结 6 1170
无人及你
无人及你 2020-11-27 02:27

I have several complex data structures like

Map< A, Set< B > >
Set< Map< A, B > >
Set< Map< A, Set< B > > >
Map<         


        
相关标签:
6条回答
  • 2020-11-27 02:58

    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;
                }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-27 02:58

    It looks like you're on the right track with XMLAdapter... the error message may be a clue:

    class java.util.Collections$UnmodifiableMap nor any of its super class is known to this context.

    are you wrapping a map using Collections.unmodifiableMap() anywhere? Where exactly does the error occur?


    (previous answer left as a stale record for the curious)

    You can create custom marshaller/unmarshaller logic that works a little more straighforward than the Adapters idea (I think; I haven't used that one before).

    Basically the idea is that you specify a static function to do the work, and you can also create a custom class. (I usually put the static function in the class in question, but you don't have to.) Then you put a line in your .XJB file to tell JAXB to use your static function.

    Now that I took a look at my existing code, I see that all I was doing was converting an attribute string to a custom Java object. Here's the code, for reference, but it's just for attributes.

    JAXB file:

    <?xml version="1.0" ?>
    <jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        jaxb:version="2.0"> 
        <jaxb:bindings schemaLocation={your schema} node="/xsd:schema">
            <jaxb:bindings node={some XPATH expression to select a node}>
                <jaxb:bindings node={maybe another XPATH relative to the above}>
                    <jaxb:property>
                        <jaxb:baseType>
                            <jaxb:javaType name={your custom Java class}
                                parseMethod={your static method for unmarshaling}
                                printMethod={your static method for marshaling}
                                />
                        </jaxb:baseType>
                    </jaxb:property>
                </jaxb:bindings>
            </jaxb:bindings>
        </jaxb:bindings>
    </jaxb:bindings>
    

    (parseMethod and printMethod convert to/from attribute strings)

    0 讨论(0)
  • 2020-11-27 03:00

    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
    @XmlRootElement
    public 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
    @XmlRootElement
    public 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
    @XmlRootElement
    public 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 annoying and (in my case) unnecessary xsi:type attributes?

    0 讨论(0)
  • 2020-11-27 03:05

    Here is my marshaller/unmarshaller for the list of @XmlType class.

    E.g

    //Type to marshall
    @XmlType(name = "TimecardForm", propOrder = {
    "trackId",
    "formId"
    }) 
    public class TimecardForm {
    
        protected long trackId;
        protected long formId;
        ...
    }
    
    //a list holder
    @XmlRootElement
    public class ListHodler<T> {
        @XmlElement
        private List<T> value ;
    
        public ListHodler() {
        }
    
        public ListHodler(List<T> value) {
            this.value = value;
        }
    
        public List<T> getValue() {
            if(value == null)
                value = new ArrayList<T>();
            return this.value;
        }
    }
    
    //marshall collection of T
    public static <T> void marshallXmlTypeCollection(List<T> value,
            Class<T> clzz, OutputStream os) {
        try {
            ListHodler<T> holder = new ListHodler<T>(value);
            JAXBContext context = JAXBContext.newInstance(clzz,
                    ListHodler.class);
            Marshaller m = context.createMarshaller();
            m.setProperty("jaxb.formatted.output", true);
    
            m.marshal(holder, os);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
    
    //unmarshall collection of T
    @SuppressWarnings("unchecked")
    public static <T> List<T> unmarshallXmlTypeCollection(Class<T> clzz,
            InputStream input) {
        try {
            JAXBContext context = JAXBContext.newInstance(ListHodler.class, clzz);
            Unmarshaller u = context.createUnmarshaller();
    
            ListHodler<T> holder = (ListHodler<T>) u.unmarshal(new StreamSource(input));
    
            return holder.getValue();
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    
        return null;
    }
    
    0 讨论(0)
  • 2020-11-27 03:06

    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 {
    
    @XmlAttribute
    public String ip;
    
    @XmlElement
    public List<LinkCountMapType> request_limit = new ArrayList<LinkCountMapType>();
    
    }
    

    LinkCountMapType Class:

    public class LinkCountMapType {
    @XmlAttribute
    public String service;
    
    @XmlValue
    public Integer count;
    }
    

    Finally the MapAdaptor Class:

        public final class MapAdapter extends XmlAdapter<MapType, Map<String, Map<String, Integer>>> {
    
    @Override
    public 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;
    }
    
    @Override
    public 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>
    
    0 讨论(0)
  • 2020-11-27 03:13

    To fix this for JSON do: jackson with jaxb

    <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
    
    0 讨论(0)
提交回复
热议问题