JAXB:我应该如何编组复杂的嵌套数据结构?

时间:2009-05-03 23:31:21

标签: java xml data-binding data-structures jaxb

我有几个复杂的数据结构,比如

Map< A, Set< B > >
Set< Map< A, B > >
Set< Map< A, Set< B > > >
Map< A, Map< B, Set< C > > >
and so on (more complex data structures)

注意:在我的情况下,如果我使用Set或List并不重要。

现在我知道JAXB允许我定义 XmlAdapter ,这很好, 但我不想为每个给定的数据结构定义一个XmlAdapter (这将是太多的复制和粘贴代码)。

我尝试通过声明两个通用XmlAdapter来实现我的目标:

  • 一张地图:MapAdapter<K,V>
  • 一个用于设置:SetAdapter<V>

问题
JAXB抱怨如下:

javax.xml.bind.JAXBException:
class java.util.Collections$UnmodifiableMap nor any of its
  super class is known to this context.

这是我的适配器类:

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

public class Adapters {

public final static class MapAdapter<K, V>
        extends XmlAdapter<MapAdapter.Adapter<K, V>, Map<K, V>> {

    @XmlType
    @XmlRootElement
    public final static class Adapter<K, V> {

        @XmlElement
        protected List<MyEntry<K, V>> key = new LinkedList<MyEntry<K, V>>();

        private Adapter() {
        }

        public Adapter(Map<K, V> original) {
            for (Map.Entry<K, V> entry : original.entrySet()) {
                key.add(new MyEntry<K, V>(entry));
            }
        }

    }

    @XmlType
    @XmlRootElement
    public final static class MyEntry<K, V> {

        @XmlElement
        protected K key;

        @XmlElement
        protected V value;

        private MyEntry() {
        }

        public MyEntry(Map.Entry<K, V> original) {
            key = original.getKey();
            value = original.getValue();
        }

    }

    @Override
    public Adapter<K, V> marshal(Map<K, V> obj) {
        return new Adapter<K, V>(obj);
    }

    @Override
    public Map<K, V> unmarshal(Adapter<K, V> obj) {
        throw new UnsupportedOperationException("unmarshalling is never performed");
    }

}

}

这是我的JUnit测试用例:

import java.io.*;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
import org.junit.*;
import static java.lang.System.*;

public class SomeTest {

@Test
public void _map2()
        throws Exception {

    Map<String, Map<String, String>> dataStructure =
            new HashMap<String, Map<String, String>>();

    Map<String, String> inner1 = new HashMap<String, String>();
    Map<String, String> inner2 = new HashMap<String, String>();

    dataStructure.put("a", inner1);
    dataStructure.put("b", inner1);

    inner1.put("a1", "1");
    inner1.put("a2", "2");
    inner2.put("b1", "1");
    inner2.put("b2", "2");

    JAXBContext context = JAXBContext.newInstance(Adapters.XMap.class,
            Adapters.XCount.class, Adapters.XEntry.class);

    Marshaller marshaller = context.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    marshaller.setAdapter(new Adapters.MapAdapter());

    StringWriter sw = new StringWriter();

    marshaller.marshal(dataStructure, sw);
    out.println(sw.toString());
}

}

7 个答案:

答案 0 :(得分:21)

我已经解决了没有XmlAdapter的问题

我为地图 Map.Entry 集合编写了带注释的JAXB对象。
主要思想是方法 xmlizeNestedStructure(...)

看看代码:

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

}

}

有效!

让我们看一下演示输出

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

抱歉,演示输出还使用了名为“count”的数据结构 这在适配器的源代码中没有提到。

BTW:有谁知道如何删除所有这些烦人的 和(在我的情况下)不必要的 xsi:type 属性?

答案 1 :(得分:5)

我有同样的要求使用Map&lt;串,地图&LT;字符串,整数&GT;取代。我使用了XMLAdapter,它运行良好。使用XMLAdaptor是我认为最干净的解决方案。下面是适配器的代码。 这是jaXb类代码片段。

    @XmlJavaTypeAdapter(MapAdapter.class)
    Map<String, Map<String, Integer>> mapOfMap = new HashMap<String,Map<String, Integer>>();

MapType类:

public class MapType {

public List<MapEntryType> host = new ArrayList<MapEntryType>();

}

MapEntry类型类:

public class MapEntryType {

@XmlAttribute
public String ip;

@XmlElement
public List<LinkCountMapType> request_limit = new ArrayList<LinkCountMapType>();

}

LinkCountMapType类:

public class LinkCountMapType {
@XmlAttribute
public String service;

@XmlValue
public Integer count;
}

最后是MapAdaptor类:

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

}

编组Jaxb对象将提供以下XML

     <mapOfmap>
    <host ip="127.0.0.1">
        <request_limit service="service1">7</request_limit>
        <request_limit service="service2">8</request_limit>
    </host>
</mapOfmap>

答案 2 :(得分:2)

以下是基于Ivan代码的“dexmlize”能力的代码。用法:

Map<String, List> nameMapResult = (Map<String, List>) Adapters.dexmlizeNestedStructure(unmarshallResult);

为了恢复集合和映射类,将使用xmlized新字段来记录类信息。详细代码:

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

}

答案 3 :(得分:1)

看起来你正在使用XMLAdapter正确的轨道...错误信息可能是一个线索:

  

类   java.util.Collections中的$ UnmodifiableMap   也不知道它的任何超类   这个背景。

你在任何地方使用Collections.unmodifiableMap()包装地图吗?错误究竟发生在哪里?


(之前的答案留给了好奇的陈旧记录)

<击> 你可以创建自定义编组器/解组器逻辑,它比适配器的想法更直接(我想;我以前没用过那个)。

<击>

基本上我们的想法是指定一个静态函数来完成工作,你也可以创建一个自定义类。 (我通常把静态函数放在有问题的类中,但你不必这样做。)然后你在.XJB文件中添加一行来告诉JAXB使用你的静态函数。

现在我看了一下我现有的代码,我发现我所做的就是将属性字符串转换为自定义Java对象。这是代码,供参考,但它仅适用于属性。

JAXB文件:

<?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和printMethod转换为/来自属性字符串)

答案 4 :(得分:1)

这是@mamlType类列表的marshaller / unmarshaller。

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

答案 5 :(得分:1)

使用复杂的模式结构时 - JAXB绑定对于解决冲突的对象至关重要。 Sourceforge上的CAM编辑器工具允许您自动创建JAXB绑定 - 有关详细信息,请参阅此处的快速指南 - http://www.cameditor.org/#JAXB_Bindings

答案 6 :(得分:1)

要解决此问题,请执行以下操作: jackson with jaxb

<init-param>
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>