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) 

注意:在我的情况下,如果使用设置或列表并不重要。

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

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

  • 一个用于Map: MapAdapter<K,V>
  • 一个用于Set: 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"); } } } 

这是我的JUnittesting用例:

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

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

我为MapMap.EntryCollection编写了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”的数据结构,这在适配器的源代码中没有提到。

顺便说一句:有谁知道如何去除所有这些恼人的(在我的情况下)不必要的xsi:type属性?

我有相同的要求使用地图<string,地图<string,整数>>。 我用XMLAdapter,它工作正常。 使用XMLAdaptor是我认为的最干净的解决scheme。 以下是适配器的代码。 这是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>(); } 

MapEntrytypes类:

 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> 

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

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

为了恢复集合和map类,一个新的字段将被xml化来logging类的信息。 详细代码:

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

它看起来像你在正确的轨道与XMLAdapter …错误消息可能是一个线索:

类java.util.Collections $ UnmodifiableMap或者它的任何超类在这个上下文中是已知的。

你是否在使用Collections.unmodifiableMap()来包装地图? 错误发生在哪里?


(以前的答案留下来是一个好奇的logging)

你可以创build自定义的编组/解组逻辑,比Adapters的想法更直接一点(我想,我以前没有使用过)。

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

现在我看了一下现有的代码,看到我所做的只是将属性string转换为自定义的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转换为/从属性string)

这是我的@XmlType类列表的编组/解组器。

例如

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

处理复杂的模式结构时 – JAXB绑定对于解决冲突对象至关重要。 Sourceforge上的CAM Editor工具允许您自动创buildJAXB绑定 – 请参阅此处的快速指南以获取更多详细信息 – http://www.cameditor.org/#JAXB_Bindings

为了解决这个JSON做: jackson与jaxb

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