April 15, 2011

JAXB and JSON via Jettison - Namespace Example

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.

<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:
 

6 comments:

  1. Thanks for this post!!

    ReplyDelete
  2. Is there any way to produce an json output like this from the above example using jettison mapped convention: {"@id":"123","prop.name":"Jane Doe"}

    Basically, 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

    ReplyDelete
    Replies
    1. Hi Soumik,

      Using 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

      Delete
    2. Hi Blaise,

      Thank you for your reply. I'm using Jboss app server 7.1.

      -Soumik

      Delete
    3. Hi Soumik,

      These 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

      Delete
    4. Hi Blaise,

      I 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

      Delete