[英]JAXB @XmlAnyElement with XmlAdapter does not make a call to the unmarshal method
I am trying to unmarshal the XML
and map it to the Java POJO.我正在尝试将
XML
和 map 解组为 Java POJO。 My XML can have some of the user-defined elements which can be random so I would like to store them.我的 XML 可以有一些用户定义的元素,这些元素可以是随机的,所以我想存储它们。 After researching I found that I can use
@XmlAnyElement(lax=true)
.经过研究,我发现我可以使用
@XmlAnyElement(lax=true)
。 I am trying to use the XMLAdapter
along with the @XmlAnyElement
but for some reason, the method unmarshal
within my XMLAdapter
is not being called at all due to which I am unable to map the user-defined
fields.我正在尝试将
XMLAdapter
与@XmlAnyElement
一起使用,但由于某种原因,我的XMLAdapter
中的unmarshal
组方法根本没有被调用,因此我无法 map user-defined
字段。
Can someone please explain to me how to unmarshal
the user-defined
fields to my Map<String,Object>
for the marshalling
everything is working fine and I am using the approach mentioned here .有人可以向我解释如何将
user-defined
字段unmarshal
组到我的Map<String,Object>
以便marshalling
一切正常,我正在使用这里提到的方法。 But unmarshalling
is not being called at all which is bit confusing for me.但是根本没有调用
unmarshalling
,这让我有点困惑。
Following is my XML
which needs to be unmarshalled
.以下是我的
XML
需要unmarshalled
组。 (Please note that the namespace
can be dynamic and user-defined which may change in every xml): (请注意,
namespace
可以是动态的和用户定义的,可能会在每个 xml 中更改):
<Customer xmlns:google="https://google.com">
<name>Batman</name>
<google:main>
<google:sub>bye</google:sub>
</google:main>
</Customer>
Following is my Customer
class to which XML
needs to be unmarshalled
;以下是我的
Customer
XML
需要unmarshalled
组 XML ;
@XmlRootElement(name = "Customer")
@XmlType(name = "Customer", propOrder = {"name", "others"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private String name;
@XmlAnyElement(lax = true)
@XmlJavaTypeAdapter(TestAdapter.class)
private Map<String, Object> others = new HashMap<>();
//Getter Setter and other constructors
}
Following is my XMLAdapter (TestAdapter)
class which will be used for marshalling
and unmarshalling
the user-defined
fields.以下是我的
XMLAdapter (TestAdapter)
unmarshalling
,它将用于marshalling
和解组user-defined
字段。 The unmarshalling
method is not being called at all.根本没有调用
unmarshalling
方法。 However the marshalling
method works as expected based on the code provided here .然而,基于此处提供的代码,
marshalling
方法按预期工作。
class TestAdapter extends XmlAdapter<Wrapper, Map<String,Object>> {
@Override
public Map<String,Object> unmarshal(Wrapper value) throws Exception {
//Method not being called at all the following SYSTEM.OUT is NOT PRINTED
System.out.println("INSIDE UNMARSHALLING METHOD TEST");
System.out.println(value.getElements());
return null;
}
@Override
public Wrapper marshal(Map<String,Object> v) throws Exception {
return null;
}
}
class Wrapper {
@XmlAnyElement
List elements;
}
I have used the package-info.java
file and it has been filled with following contents:我使用了
package-info.java
文件,里面填充了以下内容:
@jakarta.xml.bind.annotation.XmlSchema(namespace = "http://google.com", elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED)
package io.model.jaxb;
I researched a lot but could not find any answer which does something similar.我进行了很多研究,但找不到任何类似的答案。 Also, tried a lot of things but none worked.
此外,尝试了很多东西,但都没有奏效。 Hence, posting the same here and looking for some suggestion or workaround.
因此,在这里发布相同的内容并寻找一些建议或解决方法。
I have few doubts with regards to unmarshalling
:关于
unmarshalling
,我几乎没有疑问:
XMLAdapter TestAdapter.class
unmarshal
method is not being called during the unmarshalling?XMLAdapter TestAdapter.class
unmarshal
组方法?unmarshal
the XML fields which can appear randomly with namespaces?unmarshal
组可能随命名空间随机出现的 XML 字段?*** FOLLOWING IS EDITED SECTION BASED ON RESPONSE FROM Thomas Fritsch ****
Based on the response I have edited my class but still not working as expected:根据回复,我编辑了我的 class 但仍无法按预期工作:
@XmlRootElement(name = "Customer")
@XmlType(name = "Customer", propOrder = {"name", "others"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private String name;
@JsonIgnore
@XmlJavaTypeAdapter(TestAdapter.class)
private List<Object> others;
@XmlTransient
@XmlAnyElement(lax = true)
private List<Element> another = new ArrayList<>();
}
So what's happening is that if I use @XmlTransient
then the field another
will not be populated during the unmarshalling
and I need to keep it @XmlTransient
because I do not want it during the marshalling
similarly I have made @JsonIgnore
for Map<String, Object> others
because I do not need it during the unmarshalling
but both things are conflicting with each other and not able to obtain the the required output.所以发生的事情是,如果我使用
@XmlTransient
,那么在unmarshalling
组期间将不会填充another
字段,我需要保留它@XmlTransient
,因为我在marshalling
@JsonIgnore
中不想要它,类似地我为Map<String, Object> others
,因为我在unmarshalling
组过程中不需要它,但两者相互冲突,无法获得所需的 output。
My main goal is to convert the XML
file to JSON
and vice versa.我的主要目标是将
XML
文件转换为JSON
,反之亦然。 For the above mentioned XML
file I would like to obtain the following output in JSON
:对于上面提到的
XML
文件,我想在 JSON 中获得以下JSON
:
{
"Customer": {
"name": "BATMAN",
"google:main": {
"google:sub": "bye"
}
}
}
Similarly when I convert this JSON
then I should get the original XML
.同样,当我转换此
JSON
时,我应该得到原始的XML
。
Following is my Main.class
:以下是我的
Main.class
:
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);
}
}
The Map
type of your others
property is not suitable for @XmlAnyElement
.您
others
属性的Map
类型不适合@XmlAnyElement
。
According to the its javadoc the @XmlAnyElement
annotation is meant to be used with a List
or an array property (typically with a List
or array of org.w3c.dom.Element
).根据其 javadoc,
@XmlAnyElement
注释旨在与List
或数组属性一起使用(通常与List
或org.w3c.dom.Element
数组一起使用)。 May be you have confused this with the @XmlAnyAttribute
annotation which indeed is used with a Map
property.可能您已经将此与
@XmlAnyAttribute
注释混淆了,后者确实与Map
属性一起使用。
Hence in your Customer
class the others
property without using an adapter would look like this: name;因此,在您的
Customer
class 中,不使用适配器的others
属性将如下所示:
@XmlAnyElement(lax = true)
private List<Element> others = new ArrayList<>();
And the others
property with using an adapter should look like this:使用适配器的
others
属性应如下所示:
@XmlAnyElement(lax = true)
@XmlJavaTypeAdapter(TestAdapter.class)
private List<MyObject> others = new ArrayList<>();
When doing this way, then JAXB will actually call your adapter.这样做时,JAXB 实际上会调用您的适配器。 The adapter's job is to transform between
Element
and MyObject
.适配器的工作是在
Element
和MyObject
之间进行转换。
public class TestAdapter extends XmlAdapter<Element, MyObject> {
@Override
public MyObject unmarshal(Element v) throws Exception {
...
}
@Override
public Element marshal(MyObject v) throws Exception {
...
}
}
After trying out a lot of things, I was able to get it working.在尝试了很多东西之后,我能够让它工作。 Posting the solution for the same so it can be helpful to someone in the future.
发布相同的解决方案,以便将来对某人有所帮助。
I have used Project Lombok
so you can see some additional annotations such as @Getter, @Setter, etc
我使用了
Project Lombok
,因此您可以看到一些额外的注释,例如@Getter, @Setter, etc
Method-1:
As mentioned @Thomas Fritsch you have to use the @XmlAnyElement(lax=true)
with List<Element>
I was using with the Map<String, Object>
.正如@Thomas Fritsch 所提到的,您必须将
@XmlAnyElement(lax=true)
与List<Element>
我与Map<String, Object>
一起使用。
Method-2:
You can continue to use Map<String,Object>
and use @XmlPath(".")
with adapter to get it working.您可以继续使用
Map<String,Object>
并将@XmlPath(".")
与适配器一起使用以使其正常工作。 Posting the code for the same here: (I have added one additional field age
other that everything remain the same)在这里发布相同的代码:(我添加了一个额外的字段
age
,其他一切都保持不变)
@XmlRootElement(name = "Customer")
@XmlType(name = "Customer", propOrder = {"name", "age", "others"})
@XmlAccessorType(XmlAccessType.FIELD)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
@XmlElement(name = "name")
private String name;
private String age;
@XmlJavaTypeAdapter(TestAdapter.class)
@XmlPath(".")
private Map<String, Object> others;
}
Following is the TestAdapter.class
posting the same for reference.以下是发布相同内容的
TestAdapter.class
以供参考。 When you unmarhsal
the method unmarshal
in TestAdapter
will get called and you can do anything you need.当你解组时,
unmarhsal
中的unmarshal
TestAdapter
方法将被调用,你可以做任何你需要的事情。
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;
}
Although it works for specific cases, I am seeing one problem by using this approach.尽管它适用于特定情况,但使用这种方法我发现了一个问题。 I have created a new post for this issue.
我为此问题创建了一个新帖子。 If I get any response for that issue then I will try to update the code so it can work correctly.
如果我对此问题有任何回应,那么我将尝试更新代码,使其能够正常工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.