JAXB Nesting a Collection - java

I'm having troubles mapping a Collection of JAXB object within another JAXB object, anyone see the issue with my structure below? I get an empty formerUsers ArrayList using the following code:
String test="<SSO-Request><User-Id>3119043033121014002</User-Id><Former-User-Ids><User-Id>3119043033121014999</User-Id><User-Id>3119043033121014555</User-Id></Former-User-Ids></SSO-Request>";
SSORequest ssoRequest=null;
try{
JAXBContext jaxbContext = JAXBContext.newInstance(SSORequest.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
ssoRequest = (SSORequest) unmarshaller.unmarshal(new StringReader(test));
}
catch(Exception e){
e.printStackTrace();
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="SSO-Request")
public class SSORequest {
#XmlElement(name="User-Id")
String userId;
#XmlElementWrapper(name="Former-User-Ids")
List<FormerUser> formerUsers;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="Former-User-Ids")
public class FormerUser {
#XmlElement(name="User-Id")
String userId;
}

You're over-complicating your mapping, this is all you need:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="SSO-Request")
public class SSORequest {
#XmlElement(name="User-Id")
String userId;
#XmlElementWrapper(name="Former-User-Ids")
#XmlElement(name="User-Id")
List<String> formerUserIds;
}

You should either change your mapping as skaffman proposed, or you should change the xml:
<SSO-Request><User-Id>3119043033121014002</User-Id><Former-User-Ids><Former-User><User-Id>3119043033121014999</User-Id></Former-User><Former-User><User-Id>3119043033121014555</User-Id></Former-User></Former-User-Ids></SSO-Request>
and change the name of the the FormerUser xml element:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="Former-User")
public class FormerUser {
#XmlElement(name="User-Id")
String userId;
}

If the property should be List<FormerUser> then you will need a way to tell JAXB what the ID corresponds to. If the data for FormerUsers will occur in the document then you can use #XmlID and #XmlIDREF for this mapping:
http://bdoughan.blogspot.com/2010/10/jaxb-and-shared-references-xmlid-and.html
If the data for FormerUsers will occur outside the document, then you can use the strategy I described in the answer below:
Using JAXB to cross reference XmlIDs from two XML files

Related

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. My XML can have some of the user-defined elements which can be random so I would like to store them. After researching I found that I can use #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.
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. But unmarshalling is not being called at all which is bit confusing for me.
Following is my XML which needs to be unmarshalled. (Please note that the namespace can be dynamic and user-defined which may change in every 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;
#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. The unmarshalling method is not being called at all. However the marshalling method works as expected based on the code provided here.
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:
#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:
Why my XMLAdapter TestAdapter.class unmarshal method is not being called during the unmarshalling?
How can I unmarshal the XML fields which can appear randomly with namespaces?
Am I doing something wrong or is there something else I should do to read the namespaces and elements which appear dynamically?
*** 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:
#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.
My main goal is to convert the XML file to JSON and vice versa. For the above mentioned XML file I would like to obtain the following output in JSON:
{
"Customer": {
"name": "BATMAN",
"google:main": {
"google:sub": "bye"
}
}
}
Similarly when I convert this JSON then I should get the original XML.
Following is my 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.
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).
May be you have confused this with the #XmlAnyAttribute annotation
which indeed is used with a Map property.
Hence in your Customer class the others property without using an adapter would look like this:
name;
#XmlAnyElement(lax = true)
private List<Element> others = new ArrayList<>();
And the others property with using an adapter should look like this:
#XmlAnyElement(lax = true)
#XmlJavaTypeAdapter(TestAdapter.class)
private List<MyObject> others = new ArrayList<>();
When doing this way, then JAXB will actually call your adapter.
The adapter's job is to transform between Element and 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
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>.
Method-2:
You can continue to use Map<String,Object> and use #XmlPath(".") with adapter to get it working. Posting the code for the same here: (I have added one additional field age other that everything remain the same)
#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. When you unmarhsal the method unmarshal in TestAdapter will get called and you can do anything you need.
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.

How to marshal xml to a jaxb wrapper class?

<myresponse>
<field1></field1>
<field2></field2>
//...
</myresponse>
#XmlRootElement(name = "myresponse")
#XmlAccessorType(XmlAccessType.FIELD)
public class MyResponse {
//fields
}
The following would work fine:
JAXBContext jaxbContext = JAXBContext.newInstance(MyResponse.class);
Problem: I'd like to create a wrapper class around that response, and marshall it into this new class. But how, if the XML structure remains the same?
public class CustomResponse {
#XmlElement(name = "myresponse")
private MyResponse myresponse;
//some more DTO fields or bean logic
}
JAXBContext.newInstance(CustomResponse.class);
How can I tell jaxb to actually map anything from the xml response into the #XmlElement(name = "myresponse") object, if possible?
Sidenote: I have no control of the xml received.

any way to convert xml to object in java?

I am trying to use a XML and access to all fields and data on an easy way, so, I decided to use JaxB , but I have no idea how to create all the classes for the objects, I tried manually like this.
#XmlRootElement(name = "Response")
public class Response {
#XmlElement(ns = "SignatureValue")
String signatureValue;
}
But I get an error on #XmlElement saying the annotation is disallowed for this location...
I checked other posts and they work great if I have something like Hellw but doesnt work with more complex formats, an example of first part of mine is like this
<?xml version="1.0" encoding="UTF-8"?><DTE xsi:noNamespaceSchemaLocation="http://www.myurl/.xsd" xmlns:gs1="urn:ean.ucc:pay:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
any idea how to do all this??
Thanks in advance
EDIT:
I forgot to say, the XML is actually a String with the entire XML.
The #XmlElement annotation is valid on a field. If you have a corresponding property then you should annotate the class with #XmlAccessorType(XmlAccessType.FIELD) to avoid a duplicate mapping exception.
Java Model
Annotating the Field
#XmlRootElement(name = "Response")
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
#XmlElement(name = "SignatureValue")
String signatureValue;
public String getSignatureValue() {
return signatureValue;
}
public void setSignatureValue(String signatureValue) {
this.signatureValue = signatureValue;
}
}
Annotating the Property
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "Response")
public class Response {
String signatureValue;
#XmlElement(name = "SignatureValue")
public String getSignatureValue() {
return signatureValue;
}
public void setSignatureValue(String signatureValue) {
this.signatureValue = signatureValue;
}
}
For More Information
http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
Demo Code
Below is some demo code that reads/writes the XML corresponding to your Response class.
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Response.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum19713886/input.xml");
Response response = (Response) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<SignatureValue>Hello World</SignatureValue>
</Response>

how do I convert an xml string to an object. I don't know what object ahead of time. Just several possibities

I have a string of that is an XML string and it could correspond to one of several objects that are jaxb generated schema files.
I don't know what object it is ahead of time.
How do convert this XML string to an jaxb xml object? Some type of unmarshalling?
How do I determine which object it is assigned to?
How do I instantiate the object once it is converted from xml string to the object?
You could do something like the following:
Foo
As long as there is a root element associated with your class via an #XmlRootElement or #XmlElementDecl annotation you don't need to specify the type of class that you are unmarshalling (see: http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html).
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Foo {
private String bar;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo
To unmarshal from a String simply wrap the String in an instance of StringReader. The unmarshal operation will convert the XML into an instance of your domain class. If you don't know what class you will have to use instanceof or getClass() to determine what type it is.
import java.io.StringReader;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
String xml = "<foo><bar>Hello World</bar></foo>";
StringReader reader = new StringReader(xml);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Object result = unmarshaller.unmarshal(reader);
if(result instanceof Foo) {
Foo foo = (Foo) result;
System.out.println(foo.getBar());
}
}
}
Output
Hello World
Unmarshaller yourunmarshaller = JAXBContext.NewInstance(yourClass).createUnMarshaller();
JAXBElement<YourType> jaxb = (yourunmarshaller).unmarshal(XMLUtils.getStringSource([your object]), [the class of your object].class);
If you have schema files for the XML objects, which you would if you're using JAXB, run a validate on the XML.
Java XML validation against XSD Schema
If you generate objects from XSD, then JAXB generated an ObjectFactory class in the same package as all the type classes.
JAXBContext jaxbContext = JAXBContext.newInstance("your.package.name");
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Here "your.package.name" stands for the package name of your ObjectFactory class.
The unmarshaller can now convert your XML into objects:
public Object createObjectFromString(String messageBody) throws JAXBException {
return unmarshaller.unmarshal(new StringReader(messageBody));
}
If this is successful, a JAXBElement object will be returned:
try {
JAXBElement jaxbElement= (JAXBElement) createObjectFromString(messageBody);
} catch (JAXBException e) {
// unmarshalling was not successful, take care of the return object
}
If you have a jaxbElement object returned, you can call getValue() for the wrapped object, of getDeclaredType() for it's class.
With this method, you don't need to know the type of the target object in advance.

Why are names returned with # in JSON using Jersey

I am using the JAXB that is part of the Jersey JAX-RS. When I request JSON for my output type, all my attribute names start with an asterisk like this,
This object;
package com.ups.crd.data.objects;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
#XmlType
public class ResponseDetails {
#XmlAttribute public String ReturnCode = "";
#XmlAttribute public String StatusMessage = "";
#XmlAttribute public String TransactionDate ="";
}
becomes this,
{"ResponseDetails":{"#transactionDate":"07-12-2010",
"#statusMessage":"Successful","#returnCode":"0"}
So, why are there # in the name?
Any properties mapped with #XmlAttribute will be prefixed with '#' in JSON. If you want to remove it simply annotated your property with #XmlElement.
Presumably this is to avoid potential name conflicts:
#XmlAttribute(name="foo") public String prop1; // maps to #foo in JSON
#XmlElement(name="foo") public String prop2; // maps to foo in JSON
If you are marshalling to both XML and JSON, and you don't need it as an attribute in the XML version then suggestion to use #XmlElement is the best way to go.
However, if it needs to be an attribute (rather than an element) in the XML version, you do have a fairly easy alternative.
You can easily setup a JSONConfiguration that turns off the insertion of the "#".
It would look something like this:
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
public JAXBContextResolver() throws Exception {
this.context= new JSONJAXBContext(
JSONConfiguration
.mapped()
.attributeAsElement("StatusMessage",...)
.build(),
ResponseDetails.class);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return context;
}
}
There are also some other alternatives document here:
http://jersey.java.net/nonav/documentation/latest/json.html
You have to set JSON_ATTRIBUTE_PREFIX in your JAXBContext configuration to "" which by default is "#":
properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "");

Categories