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
.
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.
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
.
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>
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
}
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;
}
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);
}
}
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"
}
}
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"
}
}
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>
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?
** Edited **
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.
XmlPath(".")
, tryCustomer/*[not(self::name | self::age)]
or*[not(self::name | self::age)]
– Nighttime@XmlPath(".")
with@XmlPath(""Customer/*[not(self::name | self::age)])
? or replace the whole@XmlPath(".")
withCustomer/*[not(self::name | self::age)]
? I am a bit confused can you please provide an example or guide me to some documentation where these things are covered. Thanks in advance. – Liman