【问题标题】:JAXB: How should I marshal complex nested data structures?JAXB:我应该如何编组复杂的嵌套数据结构?
【发布时间】:2009-05-03 23:31:20
【问题描述】:

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

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&lt;K,V&gt;
  • 一组:SetAdapter&lt;V&gt;

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

}

【问题讨论】:

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


    【解决方案1】:

    我已经解决了没有 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 属性?

    【讨论】:

    • 酷!有时我可能不得不自己做这件事——您能否在您的 .xjb 绑定文件中发布您必须做的事情的摘要?
    • 我从未使用过 .xjb 绑定文件 ;) 建议的解决方案可以正常工作 ;)
    • 我必须在我的 JAXB 注释类中添加 @XmlSeeAlso({Adapters.XMap.class, Adapters.XCollection.class, Adapters.XEntry.class}) 才能使其正常工作。
    • 我不断收到blabla.XMap nor any of its super class is known to this context. :(
    • 你没有使用getXmlClasses() - 它有什么用?
    【解决方案2】:

    我对使用 Map> 有同样的要求。我使用了 XMLAdapter,它工作得很好。使用 XMLAdaptor 是我认为最干净的解决方案。下面是适配器的代码。 这是 jaXb 类代码 sn-p。

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

    地图类型类:

    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>
    

    【讨论】:

      【解决方案3】:

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

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

      为了恢复collection和map类,会xml化一个新的字段来记录类信息。详细代码:

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

      【讨论】:

      • 你没有使用getXmlClasses() - 它有什么用?
      【解决方案4】:

      看起来您在使用 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 与属性字符串相互转换)

      【讨论】:

      • 感谢您的回答!不,我不会在任何地方包装我的地图,这很奇怪。可能发生在 JAXB 内部的某个地方。
      • 您是否尝试过调试,例如在你的编组/解组代码上设置断点?
      • 我最终编写了自己的“包装器”,没有 XmlAdapter,请参阅我的答案。
      【解决方案5】:

      这是@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;
      }
      

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

        • 请在您的回答中更具描述性。参考:How to Answer
        • 你在写什么没有意义,你在写一个 web.xml 资源映射.....这是一个 RMI 问题,而不是一个 servlet 定义
        猜你喜欢
        • 1970-01-01
        • 2018-05-15
        • 2019-06-06
        • 1970-01-01
        • 1970-01-01
        • 2022-01-17
        • 1970-01-01
        • 2021-11-30
        相关资源
        最近更新 更多