JAXB Mapping to JSON - java

I have written a JAX-RS (Jersey) REST Service, which accepts XML messages of ONIX XML format. Generally, I have generated all the required classes for JAXB binding from the given schema with xjc. There are more than 500 classes overall and I cannot modify them.
Now, when I have a JAXB-mapped object, I need to store it to the database. I work with mongoDb, so the message format should be JSON. I tried to use Jackson with JAXB module to convert JAXB-object into JSON, which works pretty fine with storing the data. But when I try to convert the JSON back into the JAXB object, it throws an exception connected somehow with the JAXBElement. In google I found out that the JAXBElement is not supported in Jackson and I have to work around this issue. But I cant do it because I cannot modify JAXB-generated classes.
Is there a way to map JAXB Objects into JSON with some other means, but which will follow the whole JAXB specification so that I have no problems in the future converting from JSON to the JAXB object and visa vera?

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
If you can't do it with Jackson, you use case will work with MOXy.
Java Model
Foo
Here is a sample class that contains a field of type JAXBElement.
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
#XmlElementRef(name="bar")
private JAXBElement<Bar> bar;
}
Bar
public class Bar {
}
ObjectFactory
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name = "bar")
public JAXBElement<Bar> createBar(Bar bar) {
return new JAXBElement<Bar>(new QName("bar"), Bar.class, bar);
}
}
Standalone Demo Code
Below is some demo code you can run in Java SE to see that everything works:
Demo
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class, ObjectFactory.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum19158056/input.json");
Foo foo = unmarshaller.unmarshal(json, Foo.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
input.json/Output
{"bar" : {} }
Running with JAX-RS
The following links will help you leverage MOXy in a JAX-RS service:
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
http://blog.bdoughan.com/2013/06/moxy-is-new-default-json-binding.html

Since you're using Jackson you can construct an ObjectMapper with the JaxbAnnotationModule and write out the value. The following is some code to write a JAXB annotated object to system out.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JaxbAnnotationModule());
mapper.writeValue(System.out, jaxbObject);
This approach will also work on Glassfish as it does not utilize the provider given by the container which would cause ClassNotFoundException as per GLASSFISH-21141

Related

MOXy Dynamix JAXB context unmarshalls to wrong property names

I am currently trying to unmarshall the following XML file using the DynamicJaxbContext from MOXy:
<?xml version="1.0" encoding="UTF-8"?>
<request xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://somehost.com/schema_schedule_request.xsd">
<header>
<originator>MySatelliteCompany</originator>
<recipient>Recipient</ recipient>
<schedule_valid_from>2008-12-17T00:00:00Z</schedule_valid_from>
<schedule_valid_to>2008-12-18T00:00:07Z</schedule_valid_to>
<request_reference>HGT4285T3</request_reference>
<generation_time>2008-12-16T08:24:00Z</generation_time>
</header>
<body>
<schedule_request>
<start_time>2008-12-17T09:30:47Z</start_time>
<end_time>2008-12-17T09:33:47Z</end_time>
<satellite_name>MySat</satellite_name>
</schedule_request>
</body>
</request>
It works but the dynamically created Java classes' properties do not correspond to the fields given in the XML. For example: <satellite_name> is unmarshalled to "satelliteName". This makes writing a custom binding file for my backend API quite difficult, because I would have to first either unmarshall all XML files I will get as Input and manually write down the corresponding property names or write another helper app which does this for me.
Is there any way to change this MOXy behavior so it unmarshalls the field names correctly as they are in the XML?
ADDITION:
So I found why this is in the MOXy Documentation:
XML names found in the metadata (complex type names, element names,
attribute names) will be translated to Java identifiers according to
the algorithms described in "Appendix D: Binding XML Names to Java
Identifiers" of the Java Architecture for XML Binding (JAXB) 2.2
Specification (http://jcp.org/en/jsr/detail?id=222). - See more at:
http://www.eclipse.org/eclipselink/documentation/2.4/moxy/dynamic_jaxb001.htm#BABCDJDF
but my principle question still stands: is there any way to shut this off or modify this behavior?
Your ADDITION is correct, MOXy isn't unmarshalling wrong property names, it just unmarshals to property names that correspond to what the mapped property/field names would be in the generated classes.
What the Solution Should Be
binding.xml
The default XML Schema to Java naming rules in JAXB is to remove the _. You can supply a binding file to have this behaviour be different.
<jaxb:bindings
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
version="2.1">
<jaxb:globalBindings underscoreBinding="asCharInWord"/>
</jaxb:bindings>
Demo
Using MOXy's Dynamic JAXB, below is an example of how you can leverage the bindings file.
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.dynamic.DynamicEntity;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
StreamSource schemaSource = new StreamSource("src/forum20146935/schema.xsd");
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(DynamicJAXBContextFactory.EXTERNAL_BINDINGS_KEY, new StreamSource("src/forum20146935/binding.xml"));
JAXBContext jc = DynamicJAXBContextFactory.createContextFromXSD(schemaSource, null, Demo.class.getClassLoader(), properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum20146935/input.xml");
DynamicEntity root = (DynamicEntity) unmarshaller.unmarshal(xml);
System.out.println(root.get("foo_bar"));
}
}
Why Didn't it Work?
As I mentioned earlier MOXy will base the dynamic property name based on the corresponding mapped field/property generated by XJC. This happens to look something like where the property name has the _ but the corresponding mapped field does not.
#XmlElement(name = "foo_bar", required = true)
protected String fooBar;
public String getFoo_Bar() {
return fooBar;
}
public void setFoo_Bar(String value) {
this.fooBar = value;
}
What you Could Do Instead
Use the transformed key name.
You could use the getValueByXPath functionality on the MOXy JAXBContext to interact with the objects in a more XML like way.
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.dynamic.DynamicEntity;
import org.eclipse.persistence.jaxb.JAXBHelper;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
StreamSource schemaSource = new StreamSource("src/forum20146935/schema.xsd");
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(DynamicJAXBContextFactory.EXTERNAL_BINDINGS_KEY, new StreamSource("src/forum20146935/binding.xml"));
JAXBContext jc = DynamicJAXBContextFactory.createContextFromXSD(schemaSource, null, Demo.class.getClassLoader(), properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum20146935/input.xml");
DynamicEntity root = (DynamicEntity) unmarshaller.unmarshal(xml);
String fooBar = JAXBHelper.getJAXBContext(jc).getValueByXPath(root, "foo_bar/text()", null, String.class);
System.out.println(fooBar);
}
}

Marshalling the Java objects (without #XmlRootElement) to JSON via Jettison

I have done the marshalling of an JAXB object (Which contains #XmlRootElement) to JSON using Jettison. But I can not convert a simple java object which has no annotations like #XmlRootElement to JSON. I would like to know "Is it mandatory to have that #XmlRootElement to marshall an object to JSON?"
I am getting the following Exception when I try to marshall the java object to Json
com.sun.istack.SAXException2: unable to marshal type "simpleDetail" as an element because it is missing an #XmlRootElement annotation
What could be the issue?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
The JAXB (JSR-222) specification does not cover JSON-binding. Instead of using a JAXB implementation with the Jettison library, you could use EclipseLink JAXB (MOXy) that offers native JSON-binding. Below is an example.
JAVA MODEL
Foo
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
private List<Bar> mylist;
}
Bar
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Bar {
private int id;
private String name;
}
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
DEMO CODE
Demo
MOXy does not require the #XmlRootElement annotation, and you can use the JSON_INCLUDE_ROOT property to tell MOXy to ignore the presence of any #XmlRootElement annotations. When the root element is ignored you need to use an unmarshal method that takes a class parameter to specify the type you are unmarshalling.
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum15404528/input.json");
Foo foo = unmarshaller.unmarshal(json, Foo.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
input.json/Output
We see that no root element is present in the input or output.
{
"mylist" : [ {
"id" : 104,
"name" : "Only one found"
} ]
}
ADDITIONAL INFORMATION
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
Just read that #XmlRootElement is not necessary always. Please read this blog, at the bottom you will find how it's done without #XmlRootElement.
Also go through the answers in the post No #XmlRootElement generated by JAXB.

Serializing with JAXB and the Any

I have a schema that defines the following type:
<xsd:complexType name="Payload">
<xsd:sequence>
<xsd:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
</xsd:sequence>
</xsd:complexType>
And that creates an object like so:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Payload", propOrder = {
"any"
})
public class Payload {
#XmlAnyElement(lax = true)
protected List<Object> any;
}
Now I try adding another generated JAXB object to that Payload doing something like this:
Class payloadClass = ...;
JAXBContext context = JAXBContext.newInstance( WrapperRequest.class, payloadClass);
...
marshaller.marshal( wrappedRequest );
But I get a terrible exception that looks like it'll never work so I decide to serialize the payload object to XML first then add that as a string in the payload.
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance( sdoRequest.getClass() );
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(new JAXBElement(new QName("uri", sdoRequest.getClass().getSimpleName()), sdoRequest.getClass(), sdoRequest), writer);
payload.getAny().add( writer.toString() );
And this blows up with an exception saying "java.lang.String" does not contain an #XmlRootElement.
So how will the use of xs:any ever work with JAXB? Nothing seems to want to work because JAXB turns the Payload into Object, and it won't serialize just anything in Object. This is all inside Axis2 as well so it's been very challenging to get to this point.
Below I will demonstrate JAXB (JSR-222) and any with an example:
Payload
The any property is annotated with #XmlAnyElement(lax=true). This means that for that property if an element is associated with a class via #XmlRootElement or #XmlElementDecl then an instance of the corresponding object will be used to populate the property if not the element will be set as an instance of org.w3c.dom.Element.
package forum13941747;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Payload", propOrder = {
"any"
})
public class Payload {
#XmlAnyElement(lax = true)
protected List<Object> any;
}
Foo
Below is an example of a class annotated with #XmlRootElement.
package forum13941747;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Foo {
}
Bar
Below is an example of a class without the #XmlRootElement annotation. In this use case we will leverage the #XmlElementDecl annotation on a factory class (usually called ObjectFactory) annotated with #XmlRegistry.
package forum13941747;
public class Bar {
}
ObjectFactory
Below is an example of specifying an #XmlElementDecl annotation for the Bar class.
package forum13941747;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name="bar")
public JAXBElement<Bar> createBar(Bar bar) {
return new JAXBElement<Bar>(new QName("bar"), Bar.class, bar);
}
}
input.xml
Below is the input document we'll use for this example. There are 3 elements that correspond to the any property. The first corresponds to the #XmlRootElement annotation on the Foo class. The second corresponds to the #XmlElementDecl annotation for the Bar class and the third does not correspond to any of the domain classes.
<?xml version="1.0" encoding="UTF-8"?>
<payload>
<foo/>
<bar/>
<other/>
</payload>
Demo
In the demo code below we will unmarshal the input document, then output the classes of the objects in the resulting any property and then marshal the payload object back to XML.
package forum13941747;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Payload.class, Foo.class, ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum13941747/input.xml");
Payload payload = (Payload) unmarshaller.unmarshal(xml);
for(Object o : payload.any) {
System.out.println(o.getClass());
}
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(payload, System.out);
}
}
Output
Below is the output from running the demo code. Note the classes corresponding to the objects in the any property. The foo element became an instance of the Foo class. The bar element became an instance of JAXBElement that holds an instance of Bar. The other element became an instance of org.w3c.dom.Element.
class forum13941747.Foo
class javax.xml.bind.JAXBElement
class com.sun.org.apache.xerces.internal.dom.ElementNSImpl
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<payload>
<foo/>
<bar/>
<other/>
</payload>
Make use of Object Factory for mashelling the object like below you no need to have #XmlRootElement in DemoType.java .,
DemoType demoServiceRequest = new DemoType();
ObjectFactory obDemo = new ObjectFactory();
Request requestObject = new Request();
requestObject.setAny(obDemo.createDemo(demoServiceRequest));
And add DemoType class at Request.java like #XmlSeeAlso({DemoType.class})
Should your payload be a XML string, I managed to solve the very same problem using the code below:
import javax.xml.parsers.DocumentBuilderFactory;
//...
String XMLPAYLOAD = "...";
Payload payload = new ObjectFactory().createPayload();
try {
payload.setAny(DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(new InputSource(new StringReader(XMLPAYLOAD)))
.getDocumentElement());
} catch (Exception e) {
e.printStackTrace();
}
//...

Marshal empty lists as absent nodes with JAXB

Using JAXB I would like to have the possibility to marshal empty lists as absent nodes. I think that EclipseLink MOXy has that possibility, but I can't get it to work.
According to: http://wiki.eclipse.org/User:Rick.barkhouse.oracle.com/Test1 you should be able to do it like this:
#XmlElementWrapper(name="line-items", nillable=true)
#XmlNullPolicy(shouldMarshalEmptyCollections=false)
List<LineItem> item = null;
But
shouldMarshalEmptyCollections
is not a valid property.
I've tried using eclipselink 2.4.0, 2.4.1 and 2.5.0-M4. What am I doing wrong?
You could use EclipseLink JAXB (MOXy)'s #XmlPath mapping to map this use case. I'll demonstrate with an example below how it compares to using #XmlElementWrapper.
Root
package forum13268598;
import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlElementWrapper(name="line-items-element-wrapper")
List<LineItem> item1 = null;
#XmlPath("line-items-xml-path/item1")
List<LineItem> item2 = null;
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
package forum13268598;
import java.util.ArrayList;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.item1 = new ArrayList<LineItem>();
root.item2 = new ArrayList<LineItem>();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
In the #XmlElementWrapper use case an element is written out for an empty collection, but it is not for the #XmlPath use case.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<line-items-element-wrapper/>
</root>

Jersey serialize inherited property twice

I have a java bean without any annotations. And I have a class inherited from this bean with JAXB annotations.
Jersey (JAX-RS) serialize the second class to JSON. And inherited properties occur in JSON twice: with name from XmlElement annotation and with 'camel-case' name of java-bean property. Here is a code which illustrates this:
class MyBean {
private Integer beanField;
public Integer getBeanField() { return beanField; }
public void setBeanField(Integer value) { this.beanField = value; }
}
#XmlRootElement
class AnnotatedBean extends MyBean {
#Override
#XmlElement(name="field")
public Integer getBeanField() { return super.getBeanField(); }
}
}
After serialization I get the next JSON:
{
"field" : 5,
"beanField" : 5
}
(while I want it to contain only one field with name field).
I investigated JAXB marshaller implementation and found that it marshals properties from all superclasses of the given class (and that means that it's impossible to get rid of the odd beanField property in my example).
But I still hope that I could miss something. Is there a way to serialize only annotated properties?
To get only the annotated properties use XmlAccessType.NONE:
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement
class AnnotatedBean extends MyBean {
...
}
Mapping the 3rd Party Class Using Externalized Metadata
You could use the external metadata extension in EclipseLink JAXB (MOXy), I'm the tech lead. It allows you to provide metadata for 3rd party classes. For this example the metadata will look like:
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="third.party.package">
<java-types>
<java-type name="MyBean" xml-transient="true"/>
</java-types>
</xml-bindings>
To use MOXy you need to add a file named jaxb.properties in with your model classes with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
The following article has instructions on configuring MOXy to work with Jersery:
http://bdoughan.blogspot.com/2010/08/creating-restful-web-service-part-35.html
Context Resolver - Leveraging the Metadata
You would need to use a ContextResolver to get your JAXBContext to leverage the external bindings file. The metadata is specified through a property when the JAXBContext is instantiated:
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
#Provider
#Produces({"application/xml", "application/json"})
public class AnnotatedBeanContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext jaxbContext;
public PurchaseOrderContextResolver() {
try {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, new File("src/blog/bindingfile/binding.xml"));
jaxbContext = JAXBContext.newInstance(new Class[] {AnnotatedBean.class}, properties);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public JAXBContext getContext(Class<?> clazz) {
if(AnnotatedBean.class == clazz) {
return jaxbContext;
}
return null;
}
}

Categories

Resources