【问题标题】:Custom Map<Object,Object> XmlAdapter自定义 Map<Object,Object> XmlAdapter
【发布时间】:2012-03-19 17:34:21
【问题描述】:

我正在尝试将Map&lt;Integer,String&gt; XmlAdapter 改编为支持Map&lt;Object,Object&gt; 的版本。该方法基于这篇文章:XmlAdapter - JAXB's Secret Weapon

测试工具中的这一行生成NullPointerException

JAXBContext jc = JAXBContext.newInstance(Foo.class);

如果我将线束和 MapAdapter/MapEntry 更改为 T&lt;Integer,String&gt;,代码将按预期工作。

我错过了什么? Object 类型可以序列化还是需要转换为另一个不那么抽象的类?如果是这样,我认为我会在marshal() 方法中遇到这个错误,但它似乎永远不会达到这一点(至少在Netbean 的调试器中)。

MapEntryType:

public class MyMapEntryType {

    @XmlAttribute
    public Object key; 

    @XmlValue
    public Object value;

}

地图类型:

public class MyMapType {

    public List<MyMapEntryType> entry = new ArrayList<MyMapEntryType>();

}

地图适配器:

public final class MyMapAdapter extends

   XmlAdapter<MyMapType,Map<Object, Object>> {

   @Override
   public MyMapType marshal(Map<Object, Object> arg0) throws Exception {

      MyMapType myMapType = new MyMapType();

      for(Entry<Object, Object> entry : arg0.entrySet()) {

         MyMapEntryType myMapEntryType =  new MyMapEntryType();
         myMapEntryType.key = entry.getKey();
         myMapEntryType.value = entry.getValue();
         myMapType.entry.add(myMapEntryType);

      }

      return myMapType;
   }

   @Override
   public Map<Object, Object> unmarshal(MyMapType arg0) throws Exception {

      HashMap<Object, Object> hashMap = new HashMap<Object, Object>();

      for(MyMapEntryType myEntryType : arg0.entry) {
         hashMap.put(myEntryType.key, myEntryType.value);
      }

      return hashMap;
   }

}

要编组/解组的对象:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {

   @XmlJavaTypeAdapter(MyMapAdapter.class)
   Map<Object, Object> map = new HashMap<Object, Object>();

   public Map getMap() {
      return map;
   }

   public void setMap(Map map) {
      this.map = map;
   }

}

测试工具:

Map<Object,Object> xyz = new HashMap<Object,Object>();
xyz.put("key0", "value0");
xyz.put("key1", "value1");

Foo foo = new Foo();
foo.setMap(xyz);

//generates NullPointerException                
JAXBContext jc = JAXBContext.newInstance(Foo.class);

Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);

堆栈跟踪:

java.lang.NullPointerException
    at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor.get(TransducedAccessor.java:154)
    at com.sun.xml.internal.bind.v2.runtime.property.ValueProperty.<init>(ValueProperty.java:66)
    at com.sun.xml.internal.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:95)
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:145)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:479)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:498)
    at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementProperty.<init>(ArrayElementProperty.java:97)
    at com.sun.xml.internal.bind.v2.runtime.property.ArrayElementNodeProperty.<init>(ArrayElementNodeProperty.java:47)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.sun.xml.internal.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:113)
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:145)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:479)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:498)
    at com.sun.xml.internal.bind.v2.runtime.property.SingleElementNodeProperty.<init>(SingleElementNodeProperty.java:90)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.sun.xml.internal.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:113)
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:145)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:479)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:305)
    at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1100)
    at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:143)
    at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:110)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:228)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:215)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:414)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:618)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:565)

我将 Map 从 Map&lt;Object,Object&gt; 更改为 Map&lt;String,Object&gt; 以更好地代表我的需求(我还更改了所有内部对象以匹配此)。我在MyMapEntryType类的Object属性中添加了@XmlAnyElement@XmlMixed注解;我删除了@XmlValue 注释。

当我调试代码时,这一行不会产生错误(万岁):

JAXBContext jc = JAXBContext.newInstance(PropertyBag.class);

但是,尝试编组此条目:

xyz.put("key0", 1);

导致错误如下:

unable to marshal type `java.lang.Integer` as an element because it is missing an `@XmlRootElement` annotation

【问题讨论】:

  • 我可能错了,但我认为 JAXB 无法进行类型探索。在您原来的情况下,您在MyMapEntryType 中有对象。数组是对象,map 是对象,原始包装器是对象,任何其他复合类实例都是对象。 JAXB 如何仅通过查看此对象来确定要使用的序列化程序?它可以是“常见”类型的内置序列化程序,但未知类型需要链中的另一个适配器。
  • 那么,我需要一个 XmlAdapter 来为每种非常见类型提供服务吗?
  • 是的。基本类型,如 String、Date、List,所有原语都已受支持,但对于其余的,您需要以这种方式或其他方式显式声明如何(反)序列化它们。这里的要点是 JAXB 在创建上下文时构建所有类(和可能的 XML 映射)的元模型,而不是在编组完成时。对于Object 类型,JAXB 无法发现可能的编组方式。如果要隐藏在Object 后面的XML 是变化的,那么你需要走@XmlAnyElement 的方式。

标签: java map jaxb marshalling


【解决方案1】:

我只是环顾四周,发现this reply。因此,您可以使用带有自定义 XMLDOM 转换器的 @XmlAnyElement,或者您需要使用 @XmlElementRefs 明确指定此属性的可能类。

【讨论】:

    【解决方案2】:

    我能够通过在内部使用地图来解决这个问题:

    MyMapEntryType:

    public class MyMapEntryType {
    
        @XmlAttribute
        public String key; 
    
        @XmlValue
        public String value;
    
        private MyMapEntryType() {
            //Required by JAXB
        } 
    
        public MyMapEntryType(String key, String value) {
            this.key   = key;
            this.value = value;
        }
    
    }
    

    MyMapType 类:

    public class MyMapType {
    
        @XmlElement(name="Property")
        public List<MyMapEntryType> entry = new ArrayList<MyMapEntryType>();
    
    }
    
    public class MyMapAdapter extends XmlAdapter<MyMapType, Map<String,Object>> {
    
        @Override
        public MyMapType marshal(Map<String,Object> bt) throws Exception {
    
            MyMapType myMapType =  new MyMapType();
    
            for(Map.Entry<String,Object> entry : bt.entrySet()) {
    
                MyMapEntryType myMapEntryType = new MyMapEntryType(entry.getKey(), entry.getValue().toString());
                myMapType.entry.add(myMapEntryType);
    
            }
    
            return myMapType;
    
        }
    
        ...
    
    }
    

    PropertyBag (nee Foo) 类:

    @XmlRootElement(name="PropertyBag")
    public class PropertyBag {
    
        private Map<String,Object> map;
    
        public PropertyBag() {
            map = new HashMap<String,Object>();
        }
    
        @XmlJavaTypeAdapter(MyMapAdapter.class)
        @XmlElement(name="Properties")
        public Map<String,Object> getMap() {
            return map;
        }
    
        public void setMap(Map<String,Object> map) {
            this.map = map;
        }
    
    }
    

    线束:

            Map<String, Object> parent = new HashMap<String, Object>();
            parent.put("SI_EMAIL_ADDRESS", "foo@bar.edu");
            parent.put("SI_DATE", new Date());
            parent.put("SI_GUID", "ATQJj1RvgVlLqDqP_VOGltM");
            parent.put("SI_BOOLEAN", true);
            parent.put("SI_INT", 1318);
            parent.put("SI_LONG", new Long(123456789));
            parent.put("SI_INTEGER", new Integer(23456));
    
    
            PropertyBag bag = new PropertyBag();
            bag.setMap(parent);
    
            JAXBContext jc = JAXBContext.newInstance(PropertyBag.class);            
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(bag, System.out);
    

    输出:

    <PropertyBag>
        <Properties>
            <Property key="SI_INT">1318</Property>
            <Property key="SI_DATE">Wed Feb 01 22:18:35 EST 2012</Property>
            <Property key="SI_BOOLEAN">true</Property>
            <Property key="SI_LONG">123456789</Property>
            <Property key="SI_INTEGER">23456</Property>
            <Property key="SI_GUID">ATQJj1RvgVlLqDqP_VOGltM</Property>
            <Property key="SI_EMAIL_ADDRESS">foo@bar.edu</Property>
        </Properties>
    </PropertyBag>
    

    理想情况下,我希望 XML 表示为:

    <Properties>
        <SI_INT>1318</SI_INT>
        <SI_DATE>Wed Feb 01 22:18:35 EST 2012</SI_DATE>
        <SI_BOOLEAN>true</SI_BOOLEAN>
        <SI_LONG>123456789</SI_LONG>
        <SI_INTEGER>23456</SI_INTEGER>
        <SI_GUID>ATQJj1RvgVlLqDqP_VOGltM</SI_GUID>
        <SI_EMAIL_ADDRESS>foo@bar.edu</SI_EMAIL_ADDRESS>
    </Properties>
    

    我还希望能够序列化子属性:

    ...
    
    Map<String,Object> child = new HashMap<String,Object>();
    child.put("1", 33217);
    child.put("2", 36351);
    child.put("SI_TOTAL", 2);
    parent.put("SI_LIST", child);
    

    将被代表:

    <Properties>
        <SI_INT>1318</SI_INT>
        <SI_DATE>Wed Feb 01 22:18:35 EST 2012</SI_DATE>
        <SI_BOOLEAN>true</SI_BOOLEAN>
        <SI_LONG>123456789</SI_LONG>
        <SI_INTEGER>23456</SI_INTEGER>
        <SI_GUID>ATQJj1RvgVlLqDqP_VOGltM</SI_GUID>
        <SI_EMAIL_ADDRESS>foo@bar.edu</SI_EMAIL_ADDRESS>
        <SI_LIST>
            <!-- numeric elements are illegal; refactor -->
            <1>33217</1>
            <2>36351</2>
            <SI_TOTAL>2</SI_TOTAL>
        </SI_LIST>
    </Properties>
    

    但我不确定是否可以使用注解将键名转换为元素。

    【讨论】:

    • 非常感谢您的回答。基于此,我能够完成我的 XMLAdapter Marshalling 方法。但是,XmlAdapter 类中的 Unmarshalling 方法让我有点吃惊。您能否提供一些关于如何 Unmarshal 类型 Map&lt;String, Object&gt; 的想法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多