In a previous post I described how Jettison can be leveraged by a JAXB implementation to produce/consume JSON. A reader correctly pointed out that I did not describe how to handle namespaces. Since JSON does not support namespaces you would not normally include them in your mappings. However if you wanted to map your object model to both JSON and XML with namespaces, this post will demonstrate how it can be done.
Java Model
The following Java class will be used for the domain model in this example.
package-info
@XmlSchema( namespace = "http://www.example.org/package", elementFormDefault = XmlNsForm.QUALIFIED) package blog.json.ns; import javax.xml.bind.annotation.XmlSchema; import javax.xml.bind.annotation.XmlNsForm;
Customer
package blog.json.ns; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Customer { private long id; private String name; @XmlAttribute public long getId() { return id; } public void setId(long id) { this.id = id; } @XmlElement(namespace="http://www.example.org/property") public String getName() { return name; } public void setName(String name) { this.name = name; } }
XML Input
The JAXB metadata we have applied to our domain model, allow us to interact with the XML document below. We will use the following XML document to populate our domain model.
The JAXB metadata we have applied to our domain model, allow us to interact with the XML document below. We will use the following XML document to populate our domain model.
<ns:customer xmlns="http://www.example.org/property" xmlns:ns="http://www.example.org/package" id="123"> <name>Jane Doe</name> </ns:customer>
Marshal Demo
Jettison works by exposing StAX interfaces to JSON data. Since JAXB knows how to deal with StAX interfaces we can easily marshal to them. To handle the namespaces we will leverage the Config object. On the Config object we can set a Map of namespaces to JSON prefixes.
package blog.json.ns; import java.io.File; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLStreamWriter; import org.codehaus.jettison.mapped.Configuration; import org.codehaus.jettison.mapped.MappedNamespaceConvention; import org.codehaus.jettison.mapped.MappedXMLStreamWriter; public class MarshalDemo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); Customer customer = (Customer) unmarshaller.unmarshal(new File("src/blog/json/ns/input.xml")); Configuration config = new Configuration(); Map<String, String> xmlToJsonNamespaces = new HashMap<String,String>(1); xmlToJsonNamespaces.put("http://www.example.org/package", ""); xmlToJsonNamespaces.put("http://www.example.org/property", "prop"); config.setXmlToJsonNamespaces(xmlToJsonNamespaces); MappedNamespaceConvention con = new MappedNamespaceConvention(config); Writer writer = new OutputStreamWriter(System.out); XMLStreamWriter xmlStreamWriter = new MappedXMLStreamWriter(con, writer); Marshaller marshaller = jc.createMarshaller(); marshaller.marshal(customer, xmlStreamWriter); } }
JSON Output
The following JSON was produced. Note how the nodes corresponding to the
"http://www.example.org/property" namespace are prefixed with "prop.". The "prop." prefix was what we specified on the Config object.
{"customer":{"@id":"123","prop.name":"Jane Doe"}}
Unmarshal Demo
Similar to the marshal use case, we we will unmarshal from a Jettison object (MappedXMLStreamReader) that implements the StAX interface XMLStreamReader. Again the Config object is leveraged to specify namespace mappings.
package blog.json.ns; import java.util.HashMap; import java.util.Map; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLStreamReader; import org.codehaus.jettison.json.JSONObject; import org.codehaus.jettison.mapped.Configuration; import org.codehaus.jettison.mapped.MappedNamespaceConvention; import org.codehaus.jettison.mapped.MappedXMLStreamReader; public class UnmarshalDemo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); JSONObject obj = new JSONObject("{\"customer\":{\"@id\":\"123\",\"prop.name\":\"Jane Doe\"}}"); Configuration config = new Configuration(); Map<String, String> xmlToJsonNamespaces = new HashMap<String,String>(1); xmlToJsonNamespaces.put("http://www.example.org/package", ""); xmlToJsonNamespaces.put("http://www.example.org/property", "prop"); config.setXmlToJsonNamespaces(xmlToJsonNamespaces); MappedNamespaceConvention con = new MappedNamespaceConvention(config); XMLStreamReader xmlStreamReader = new MappedXMLStreamReader(obj, con); Unmarshaller unmarshaller = jc.createUnmarshaller(); Customer customer = (Customer) unmarshaller.unmarshal(xmlStreamReader); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
Further Reading
If you enjoyed this post you may also be interested in:
Thanks for this post!!
ReplyDeleteIs there any way to produce an json output like this from the above example using jettison mapped convention: {"@id":"123","prop.name":"Jane Doe"}
ReplyDeleteBasically, I don't want the json output to be wrapped inside the xmlroot element (which jettison usually does).
Also, on a different note, I've learnt a lot from the useful examples in your blog. Will you be able to show us some example to configure MOXy with jboss resteasy server, so that we can use it as a jaxb provider instead of the default jettison.
Thank you so much.
--Soumik
Hi Soumik,
DeleteUsing Jettison I am not sure how you produce the JSON that you are looking for. Ever since JSON-binding was added to MOXy I stopped investigating Jettison.
- JSON Binding with EclipseLink MOXy - Twitter Example
MOXy's MOXyJsonProvider will produce JSON like that by default, or if you are using the MOXy APIs directly you can set the JAXBContextProperties.JSON_INCLUDE_ROOT property to false.
- MOXy as Your JAX-RS JSON Provider - MOXyJsonProvider
MOXy can be used with JBoss/RESTEasy but the configuration differs depending upon the version. Which version of JBoss are you using?
-Blaise
Hi Blaise,
DeleteThank you for your reply. I'm using Jboss app server 7.1.
-Soumik
Hi Soumik,
DeleteThese instructions are second hand, but it is my understanding that you need to do the following:
1) Create a module directory like <JBOSS_HOME>/modules/org/eclipse/persistence/main
2) Copy eclipselink.jar to this directory.
3) Add a module.xml under this directory that looks like:
<module xmlns="urn:jboss:module:1.1" name="org.eclipse.persistence">
<resources>
<resource-root path="eclipselink.jar"/>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="javax.xml.bind.api"/>
</dependencies>
</module>
4) Leverage MOXyJsonProvider (see: MOXy as Your JAX-RS JSON Provider - MOXyJsonProvider)
-Blaise
Hi Blaise,
DeleteI did these. The problem I'm seeing is,
- JBOSS AS tries to add the default jax providers(jackson, jettison) by default to any jax-rs code (https://docs.jboss.org/author/display/AS7/Implicit+module+dependencies+for+deployments)
- So, I followed the steps to exclude these and add a dependency to MOXy.
- After executing, the server is not able to find any MessageBodyWriter/Reader for any resource and throwing "NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type"
- If i include the default providers, this exception is gone.
I know its more of a JBoss/RestEasy question and not related to MOXy, but hoping that you might have encountered anything like this before and can provide some solution :)
I've posted this in jboss forum as well, lets see if I get any response form there.
Thank you,
Soumik