简体   繁体   English

将自由格式XML / JSON映射到Moxy / JAXB注释类

[英]Mapping freeform XML/JSON to Moxy/JAXB annotated class

I'm trying to find a way to correctly map the following XML/JSON document to an equivalent JAXB/Moxy annotated class . 我正在尝试找到一种方法将以下XML / JSON文档正确映射到等效的JAXB / Moxy注释类
NOTE that the model element of the document, which in my example describes a person, is freeform , ie might be any kind of XML element/JSON object , which is not statically known. 请注意,文档的模型元素(在我的示例中描述了一个人)是自由形式的 ,即可能是任何类型的XML元素/ JSON对象 ,这些对象并不是静态知道的。

XML document: XML文档:

<form>
   <title>Person Form</title>
   <model>
      <person>
         <name>John</name>
         <surname>Smith</surname>
         <address>
           <street>Main St.</street>
           <city>NY</city>
           <country>USA</country>
         </address>
     <person>
   </model>
</form>

Equivalent JSON document: 等效的JSON文档:

{  
   "title":"Form Title",  
   "model":{  
      "person":{  
         "name":"John",  
         "surname":"Smith",  
         "address":{  
            "street":"Main St.",  
            "city":"NY",  
            "country":"USA"  
         }          
      }  
   }
}

I thought to map the model field as a Map , where the values might be primitive types or Map themselves . 我想将模型字段映射为Map ,其中可能是基本类型Map本身 This mapping would be enough expressive for my needs. 这种映射足以满足我的需求。

I tried to play with the @XmlReadTransformer, @XmlWriteTransformer MOXY annotations, but with no success (the record parameter I get in the buildAttributeValue is always null) 我尝试使用@XmlReadTransformer,@ XMLWriteTransformer MOXY注释,但没有成功(我在buildAttributeValue中获得的记录参数始终为null)

@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) 
public class Form {
   private String title; 
   private Model model;
   ....getters and setters....
}

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

    @XmlElement
    @XmlReadTransformer(transformerClass = Transformer.class)
    @XmlWriteTransformers({ @XmlWriteTransformer(xmlPath = "./*", transformerClass = Transformer.class) })
    private Map<String, Object> data;

    public Map<String, Object> getData() {
        return data;
    }

    public void setData(Map<String, Object> data) {
        this.data = data;
    }

    public static class Transformer implements AttributeTransformer, FieldTransformer {

        private AbstractTransformationMapping tm;

        public Transformer() {
        }

        @Override
        public void initialize(AbstractTransformationMapping tm) {
            this.tm = tm;
        }

        @Override
        public Map<String, Object> buildAttributeValue(Record r, Object o,
                Session s) {
            Map<String, Object> data = new HashMap<String, Object>();
            // TODO: ????
            return data;
        }

        @Override
        public Object buildFieldValue(Object arg0, String arg1, Session arg2) {
// TODO
            return null;
        }

    }
}

Can you suggest me a proper way of solve this problem or a different way of modeling the "model" field? 你能给我一个解决这个问题的正确方法,或者建模“模型”领域的不同方法吗?

Is Person (or whatever other class you have) a class that is available at runtime? Person(或其他任何类)是否是在运行时可用的类? Could the XML/JSON be modified slightly to include a type attribute that indicates which class it corresponds to? 可以稍微修改XML / JSON以包含指示它对应于哪个类的type属性吗? If so the example here may help you http://blog.bdoughan.com/2012/02/xmlanyelement-and-xmladapter.html 如果是这样,这里的示例可以帮助您http://blog.bdoughan.com/2012/02/xmlanyelement-and-xmladapter.html

Your Model would then have something like this 那么你的模型会有这样的东西

@XmlAnyElement
private List<Parameter> data;

and using the ParameterAdapter in the linked example you would need to include the type attribute that it is using in the XML and JSON 并且在链接示例中使用ParameterAdapter,您需要在XML和JSON中包含它正在使用的type属性

{
   "title" : "Form Title",
   "model" : {
      "person" : {
         "type" : "test.Person",
         "name" : "John",
         "surname" : "Smith",
...
}

or 要么

<form>
   <title>Person Form</title>
   <model>
      <person type="test.Person">
         <name>John</name>
         <surname>Smith</surname>
...
</form>

Would it work for you if Model keeps the data as a org.w3c.dom.Node then? 如果Model将数据保存为org.w3c.dom.Node,它会对你有用吗?

 @XmlAnyElement
 public Node data;

Then MOXy will handle this scenario but I believe there are is a bug if you read in the formatted XML and then write back out to JSON (extra newline/whitespace as a value in the output). 然后MOXy将处理这种情况,但我相信如果您读取格式化的XML然后写回JSON(额外换行/空格作为输出中的值),则存在错误。 However, with this setup I was able to read/write the XML you provided and read/write the JSON you provided. 但是,通过此设置,我能够读取/写入您提供的XML并读取/写入您提供的JSON。

There is one thing to note with the solution. 解决方案有一点需要注意。 Currently the way the JSON unmarshal reports events it tries to distingish between attributes and elements and if you read/write the JSON in your example you will notice duplicate key/value pairs. 目前,JSON解组报告事件的方式是它尝试在属性和元素之间进行区分,如果您在示例中读/写JSON,您会注意到重复的键/值对。 I've opened a bug for this issue ( https://bugs.eclipse.org/bugs/show_bug.cgi?id=407452 ) but there is a workaround which is to set the UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX property on the unmarshaller. 我已经为这个问题打开了一个错误( https://bugs.eclipse.org/bugs/show_bug.cgi?id=407452 )但是有一个解决方法是在unmarshaller上设置UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX属性。

If that JSON_ATTRIBUTE_PREFIX is not set it reports each key as an element and an attribute to see if it matches either, but if you set the JSON_ATTRIBUTE_PREFIX then only things that start with that prefix will be treated as attributes so you can set the the JSON_ATTRIBUTE_PREFIX to anything that your key names don't normally start with ie: 如果未设置JSON_ATTRIBUTE_PREFIX,则将每个键报告为元素和属性,以查看它是否匹配,但如果设置JSON_ATTRIBUTE_PREFIX,则只将以该前缀开头的内容视为属性,以便将JSON_ATTRIBUTE_PREFIX设置为你的关键名称通常不会以任何方式开头的东西,即:

   unmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, "@");  

or to be clear 或者要清楚

   unmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, "THIS_STRING_WILL_NEVER_BE_USED_AS_THE_START_OF_A_KEY_NAME");

In the example you have you will also need to set the JSON_INCLUDE_ROOT property to false as you don't have a root in the JSON ie: no wrapping "form" key in your JSON. 在您拥有的示例中,您还需要将JSON_INCLUDE_ROOT属性设置为false,因为您在JSON中没有根,即:在JSON中没有包装“form”键。

   u.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT,false);

Any JAXB (JSR-222) implementation (including EclipseLink MOXy ) can keep free from information as a DOM structure. 任何JAXB(JSR-222)实现(包括EclipseLink MOXy )都可以保持信息不受DOM结构的影响。 You could use an XmlAdapter to convert that DOM structure to/from a Map. 您可以使用XmlAdapter将该DOM结构转换为Map或从Map转换。

XmlAdapter (DomMapAdapter) XmlAdapter(DomMapAdapter)

Below is a skeleton for the XmlAdapter , I haven't actually included the logic for converting between a java.util.Map and org.w3c.dom.Document . 下面是XmlAdapter的框架,我实际上没有包含在java.util.Maporg.w3c.dom.Document之间进行转换的逻辑。

import java.util.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;

public class DomMapAdapter extends XmlAdapter<Object, Map<String, Object>> {

    private Document document;

    public DomMapAdapter() {
        try {
            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Map<String, Object> unmarshal(Object v) throws Exception {
        Document document = (Document) v;
        Map<String, Object> map = new HashMap<String, Object>();
        // TODO - Convert Document to Map
        return map;
    }

    @Override
    public Object marshal(Map<String, Object> v) throws Exception {
        // TODO - Convert Map to Document
        return document;
    }

}

Model 模型

Below is how you specify the XmlAdapter using the XmlJavaTypeAdapter annotation. 下面是使用XmlJavaTypeAdapter批注指定XmlAdapter方法。

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

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

    @XmlJavaTypeAdapter(DomMapAdapter.class)
    private Map<String, Object> data;

}

I have entered the following enhancement request that would make this use case easier. 我输入了以下增强请求,这将使此用例更容易。 Please add any information that you feel would be helpful: 请添加您认为有用的任何信息:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM