问题描述
简而言之,我想执行 unmarshalling 这里提到过,但除了 Map 我还会有一个 @XmlElement.所以一个字段用 (Map field) @XmlPath(".") 注释,另一个字段用 (String field) @XmlElement 注释,然后我想执行解组.
In short, I would like to perform the unmarshalling as mentioned here but along with Map I will have one more @XmlElement. So one field is annotated with (Map field) @XmlPath(".") and another field with (String field) @XmlElement and then I would like to perform unmarshalling.
我的应用程序的主要目标是使用 JAXB/Moxy 和 Jackson 转换 XML->JSON 和 JSON->XML> 图书馆.我正在尝试 unmarshal XML 并将其映射到 Java POJO.我的 XML 可以有一些专用元素和一些用户定义的元素,这些元素可以随机出现,所以我想将它们存储在 Map
My main goal of the application is to convert XML->JSON and JSON->XML using the JAXB/Moxy and Jackson library. I am trying to unmarshal the XML and map it to the Java POJO. My XML can have some dedicated elements and some user-defined elements which can appear random so I would like to store them in Map<String, Object>. Hence, I am making use of XMLAdapter. I am following the blog article to do so. I am not doing exactly the same but a bit different.
我面临的问题是在 unmarshalling 期间根本没有考虑专用字段.所有值都unmarshalled 到Map
The problem I am facing is during unmarshalling the dedicated fields are not taken into consideration at all. All the values are unmarshalled to Map<String.Object>. As per my understanding it's happening because of the annotation @XmlPath(".") and usage of XMLAdapter but If I remove this annotation then it won't work as expected. Can someone please help me with this issue? The marshaling works fine with both @XmlPath(".") and XMLAdapter. The problem is arising only during unmarshalling.
以下是我想转换为 JSON 的 XML:(注意:Name 和 Age是专用字段,而 others 是 user-defined 字段.)
Following is my XML that I would like to convert to JSON: (Note: Name and Age are dedicated fields and others is the user-defined field.)
<Customer xmlns:google="https://google.com"> <name>BATMAN</name> <age>2008</age> <google:main> <google:sub>bye</google:sub> </google:main> </Customer>
以下是我的 Customer 类,用于 marshaling、unmarshalling 由 Moxy 和 Jackson:(注意:Name和Age是专用字段,others是user-defined字段.我希望 others 仅存储无法直接映射到 POJO 的值,例如 google:main 及其来自上述 XML 的子项)
Following is my Customer class used for marshaling, unmarshalling by Moxy and Jackson: (Note: Name and Age are dedicated fields and others is the user-defined field. I want others to store only the values that cannot be mapped directly to POJO such as google:main and its children from above XML)
@XmlRootElement(name = "Customer") @XmlType(name = "Customer", propOrder = {"name", "age", "others"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { private String name; private String age; @XmlPath(".") @XmlJavaTypeAdapter(TestAdapter.class) private Map<String, Object> others; //Getter, Setter and other constructors }
以下是我的 TestAdapter 类,它将用于 Userdefined 字段:
Following is my TestAdapter class which will be used for the Userdefined fields:
class TestAdapter extends XmlAdapter<Wrapper, Map<String, Object>> { @Override public Map<String, Object> unmarshal(Wrapper value) throws Exception { System.out.println("INSIDE UNMARSHALLING METHOD TEST"); final Map<String, Object> others = new HashMap<>(); for (Object obj : value.getElements()) { final Element element = (Element) obj; final NodeList children = element.getChildNodes(); //Check if its direct String value field or complex if (children.getLength() == 1) { others.put(element.getNodeName(), element.getTextContent()); } else { List<Object> child = new ArrayList<>(); for (int i = 0; i < children.getLength(); i++) { final Node n = children.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { Wrapper wrapper = new Wrapper(); List childElements = new ArrayList(); childElements.add(n); wrapper.elements = childElements; child.add(unmarshal(wrapper)); } } others.put(element.getNodeName(), child); } } return others; } @Override public Wrapper marshal(Map<String, Object> v) throws Exception { Wrapper wrapper = new Wrapper(); List elements = new ArrayList(); for (Map.Entry<String, Object> property : v.entrySet()) { if (property.getValue() instanceof Map) { elements.add(new JAXBElement<Wrapper>(new QName(property.getKey()), Wrapper.class, marshal((Map) property.getValue()))); } else { elements.add(new JAXBElement<String>(new QName(property.getKey()), String.class, property.getValue().toString())); } } wrapper.elements = elements; return wrapper; } } @Getter class Wrapper { @XmlAnyElement List elements; }
最后,我的 Main 类将用于 marshaling 和 unmarshalling.另外,要转换为 JSON 和 XML.
And finally, my Main class will be used for marshaling and unmarshalling. Also, to convert to JSON and XML.
class Main { public static void main(String[] args) throws JAXBException, XMLStreamException, JsonProcessingException { //XML to JSON JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("Customer.xml"); final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); final XMLStreamReader streamReader = xmlInputFactory.createXMLStreamReader(inputStream); final Customer customer = unmarshaller.unmarshal(streamReader, Customer.class).getValue(); final ObjectMapper objectMapper = new ObjectMapper(); final String jsonEvent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer); System.out.println(jsonEvent); //JSON to XML Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal(customer, System.out); } }
当我转换 XML->JSON 时,我得到以下输出:(如果您观察字段 name 和 age 是不作为 Customer 类的专用字段,而是作为随机字段并写入 others)
When I convert the XML->JSON then I get the following output: (If you observe the fields name and age are not taken as the dedicated fields from Customer class rather its taken as random fields and written within the others)
{ "name" : "", "age" : "", "others" : { "google:main" : [ { "google:sub" : "bye" } ], "name" : "BATMAN", "age" : "2008" } }
我希望我的输出是这样的:(我希望先映射我的专用字段,然后如果有任何未知字段,然后稍后在 others MAP 中映射它们).请注意,我不想在我的 JSON 中获取 others 标记.我只想获取专用字段的字段名称.
I want my output to be something like this: (I want my dedicated fields to be mapped first then if there are any unknown fields then map them later within others MAP). Please note that I do not want to get others tag within my JSON. I want to get the names of the fields only for the dedicated fields.
{ "name": "BATMAN", "age": 2008, "google:main": { "google:sub": "bye" } }
以下是我希望在 marshaling 期间获得的 XML.另外,请注意我正在使用 @XmlPath(".") 以便在我的 XML 中没有得到 others 节点编组.
Following is the XML that I would like to get during the marshaling. Also, please note I am using @XmlPath(".") so that I do not get the others node within my XML during marshaling.
<Customer> <name>BATMAN</name> <age>2008</age> <google:main>> <google:sub>bye</google:sub> </google:main> </Customer>
marshaling 工作正常.问题是在 .unmarshaling 期间发生的,据我了解,这是因为带有 XMLAdapter 的注释 @XmlPath(".") 但是如果我删除此注释,那么它将无法按预期工作.有人可以帮我解决这个问题吗?
The marshaling is working fine. The problem is happening during .unmarshaling As per my understanding it's happening because of the annotation @XmlPath(".") with XMLAdapter but If I remove this annotation then it won't work as expected. Can someone please help me with this issue?
** 已编辑 **
我想了一些解决方法,但似乎没有什么对我有用.由于 @XmlPath("."),它们变得一团糟.仍在寻找一些想法或解决方法.任何帮助将不胜感激.
I thought of a few workarounds but nothing seems to work for me. They are getting messed up due to @XmlPath("."). Still looking for some idea or workarounds. Any help would be really appreciated.
推荐答案
啊,终于松了口气.这个问题让我很头疼,但我终于找到了解决方法.尝试了很多东西并联系了很多人,但似乎没有任何效果,我认为这是 JAXB/Moxy 库的问题.我能够找到解决方法.希望它对将来的人有所帮助,不要像我一样感到沮丧:)
ah, finally some relief. This issue ate my head a lot but I was finally able to find a workaround. Tried a lot of things and reached out to many people but nothing seems to work and I thought it's an issue from the JAXB/Moxy library. I was able to find a workaround. Hope it helps someone in the future and do not get frustrated like me :)
我使用了 2 个字段,一个带有 @XmlAnyElement(lax=true) List
I used 2 fields one with @XmlAnyElement(lax=true) List<Object> for storing the elements during the marshaling and another Map<String, Object> with custom serialization for JSON. In addition to this, I got to know that we can use beforeMarshal, afterMarshal, beforeUnmarshal, afterMarshal methods. The name itself suggests what it does.
在我的例子中,我使用 beforeMarshal 方法将未知数据从我的 Map
In my case, I used the beforeMarshal method to add the unknown data from my Map<String, Object> to List<Object> so during the marshaling values from List<Object> will be used. I removed the XMLAdapter.
另外,afterUnmarshal 方法将读取的未知元素从 List
Also, the afterUnmarshal method to add the read unknown elements from List<Object> to Map<String, Object> so Jackson can utilize it and write to JSON using CustomSearlizer.
基本上,这是一种隐藏和显示的方法.List
Basically, it's a kind of hide-and-show approach. List<Object> will be used during the unmarshalling and marshaling by JAXB/Moxy. Map<String, Object> will be used during the serialization and deserialization by Jackson.
Custome.class 与我的 beforeMarshal 和 afterUnmarshalling :(看起来有点复杂,基本上它交换如上所述的数据.我会有数据比较复杂,所以需要递归循环整理,大家可以根据自己的需要进行修改)
Custome.class with my beforeMarshal and afterUnmarshalling: (It seems bit complex basically it exchanges the data as mentioned above. I will have complex data so I need to recursively loop and arrange. You can make changes according to your need)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA") @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) @XmlRootElement(name = "Customer") @XmlType(name = "Customer", propOrder = {"name", "age", "otherElements"}) @XmlAccessorType(XmlAccessType.FIELD) @Getter @Setter @AllArgsConstructor @ToString public class Customer { @XmlTransient private String isA; private String name; private String age; @XmlAnyElement(lax = true) @JsonIgnore private List<Object> otherElements = new ArrayList<>(); @JsonIgnore @XmlTransient private Map<String, Object> userExtensions = new HashMap<>(); @JsonAnyGetter @JsonSerialize(using = CustomExtensionsSerializer.class) public Map<String, Object> getUserExtensions() { return userExtensions; } @JsonAnySetter public void setUserExtensions(String key, Object value) { userExtensions.put(key, value); } private void beforeMarshal(Marshaller m) throws ParserConfigurationException { System.out.println("Before Marshalling User Extension: " + userExtensions); ExtensionsModifier extensionsModifier = new ExtensionsModifier(); otherElements = extensionsModifier.Marshalling(userExtensions); System.out.println("Before Marshalling Final Other Elements " + otherElements); userExtensions = new HashMap<>(); } private void afterUnmarshal(Unmarshaller m, Object parent) throws ParserConfigurationException { System.out.println("After Unmarshalling : " + otherElements); ExtensionsModifier extensionsModifier = new ExtensionsModifier(); userExtensions = extensionsModifier.Unmarshalling(otherElements); otherElements = new ArrayList(); } }
然后是 ExtensionsModifier.class 将被 beforeMarshal 和 afterUnmarshalling 方法调用:
Then the ExtensionsModifier.class which will be called by beforeMarshal and afterUnmarshalling method:
import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ExtensionsModifier { private javax.xml.parsers.DocumentBuilderFactory documentFactory; private javax.xml.parsers.DocumentBuilder documentBuilder; private org.w3c.dom.Document document; public ExtensionsModifier() throws ParserConfigurationException { documentFactory = DocumentBuilderFactory.newInstance(); documentBuilder = documentFactory.newDocumentBuilder(); document = documentBuilder.newDocument(); } public List<Object> Marshalling(Map<String, Object> userExtensions) throws ParserConfigurationException { if (userExtensions == null) { return null; } List<Object> tempElement = new ArrayList<>(); for (Map.Entry<String, Object> property : userExtensions.entrySet()) { Element root = document.createElement(property.getKey()); if (property.getValue() instanceof Map) { List<Object> mapElements = Marshalling((Map<String, Object>) property.getValue()); mapElements.forEach(innerChildren -> { if (innerChildren instanceof Element) { if (((Element) innerChildren).getTextContent() != null) { root.appendChild(document.appendChild((Element) innerChildren)); } } }); tempElement.add(root); } else if (property.getValue() instanceof String) { root.setTextContent(((String) property.getValue())); tempElement.add(root); } else if (property.getValue() instanceof ArrayList) { for (Object dupItems : (ArrayList<Object>) property.getValue()) { if (dupItems instanceof Map) { Element arrayMap = document.createElement(property.getKey()); List<Object> arrayMapElements = Marshalling((Map<String, Object>) dupItems); arrayMapElements.forEach(mapChildren -> { if (mapChildren instanceof Element) { if (((Element) mapChildren).getTextContent() != null) { arrayMap.appendChild(document.appendChild((Element) mapChildren)); } } }); tempElement.add(arrayMap); } else if (dupItems instanceof String) { Element arrayString = document.createElement(property.getKey()); arrayString.setTextContent((String) dupItems); tempElement.add(arrayString); } } } } return tempElement; } public Map<String, Object> Unmarshalling(List<Object> value) { if (value == null) { return null; } final Map<String, Object> extensions = new HashMap<>(); for (Object obj : value) { org.w3c.dom.Element element = (org.w3c.dom.Element) obj; final NodeList children = element.getChildNodes(); //System.out.println("Node Name : " + element.getNodeName() + " Value : " + element.getTextContent()); List<Object> values = (List<Object>) extensions.get(element.getNodeName()); if (values == null) { values = new ArrayList<Object>(); } if (children.getLength() == 1) { values.add(element.getTextContent()); extensions.put(element.getNodeName(), values); } else { List<Object> child = new ArrayList<>(); for (int i = 0; i < children.getLength(); i++) { final Node n = children.item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { List<Object> childElements = new ArrayList(); childElements.add(n); values.add(Unmarshalling(childElements)); child.add(Unmarshalling(childElements)); } } extensions.put(element.getNodeName(), values); } } return extensions; } }
以下是我的 CustomSearlizer,Jackson 将使用它来创建 JSON:
Following is my CustomSearlizer which will be used by Jackson to create JSON:
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import java.util.ArrayList; import java.util.Map; public class CustomExtensionsSerializer extends JsonSerializer<Map<String, Object>> { private static final ObjectMapper mapper = new ObjectMapper(); @Override public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException { System.out.println("Custom Json Searlizer: " + value); recusiveSerializer(value, gen, serializers); } public void recusiveSerializer(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException { for (Map.Entry<String, Object> extension : value.entrySet()) { if (extension.getValue() instanceof Map) { //If instance is MAP then call the recursive method recusiveSerializer((Map) extension.getValue(), gen, serializers); } else if (extension.getValue() instanceof String) { //If instance is String directly add it to the JSON gen.writeStringField(extension.getKey(), (String) extension.getValue()); } else if (extension.getValue() instanceof ArrayList) { //If instance if ArrayList then loop over it and add it to the JSON after calling recursive method //If size more than 1 add outer elements if (((ArrayList<Object>) extension.getValue()).size() > 1) { gen.writeFieldName(extension.getKey()); gen.writeStartObject(); for (Object dupItems : (ArrayList<Object>) extension.getValue()) { if (dupItems instanceof Map) { recusiveSerializer((Map) dupItems, gen, serializers); } else { gen.writeStringField(extension.getKey(), (String) dupItems); } } gen.writeEndObject(); } else { for (Object dupItems : (ArrayList<Object>) extension.getValue()) { if (dupItems instanceof Map) { gen.writeFieldName(extension.getKey()); gen.writeStartObject(); recusiveSerializer((Map) dupItems, gen, serializers); gen.writeEndObject(); } else { gen.writeStringField(extension.getKey(), (String) dupItems); } } } } } } }
如果我提供如下输入 XML:
If I provide input as following XML:
<Customer xmlns:google="https://google.com"> <name>Rise Against</name> <age>2000</age> <google:main> <google:sub>MyValue</google:sub> <google:sub>MyValue</google:sub> </google:main> </Customer>
然后我得到以下 JSON 作为输出:
Then I get the following JSON as output:
{ "isA" : "Customer", "name" : "Rise Against", "age" : "2000", "google:main" : { "google:sub" : "MyValue", "google:sub" : "MyValue" } }
Viceversa 也可以正常工作.希望很清楚,如果不发表评论会尝试回复.
Viceversa would also work fine. Hope it's clear if not leave a comment will try to respond.