July 12, 2013

Oracle WebLogic 12.1.2 Now With EclipseLink MOXy JSON-Binding

Oracle WebLogic 12.1.2 is now available.  WebLogic 12.1.2 contains EclipseLink 2.4.2, this means that for the first time EclipseLink MOXy's JSON-binding is available in WebLogic out of the box.  I will demonstrate the benefits of using MOXy for JSON-binding with an example.

Java Model

Below is the Java model we will use for this post.  The same metadata will be used to customize the XML and JSON produced by our JAX-RS service.  The get/set methods have been omitted to save space.

Customer 

Below is a simple representation of a customer.  I have annotated a couple of the fields with MOXy's @XmlPath extension (see:  XPath Based Mapping) to demonstrate where MOXy is really being used.

package org.example.model;

import java.util.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlAttribute(name="id")
    private int identifier;
   
    @XmlPath("personalInfo/firstName/text()")
    private String firstName;
    
    @XmlPath("personalInfo/lastName/text()")
    @XmlElement(nillable=true)
    private String lastName;

    @XmlElementWrapper
    @XmlElement(name="phoneNumber")
    private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();

}

PhoneNumber 

We will map the PhoneNumber class to a complex type with simple content (see:  JAXB and Complex Types with Simple Content) to see the impact on JSON-binding.

package org.example.model;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {

    @XmlAttribute
    private String type;
    
    @XmlValue
    private String value;

}

package-info 

We will leverage the package level @XmlSchema annotation to namespace qualify the resulting XML (see: JAXB & Namespaces) so that we can see the impact on the JSON representation.

@XmlSchema(
    namespace="http://www.example.org/model",
    elementFormDefault=XmlNsForm.QUALIFIED)
package org.example.model;

import javax.xml.bind.annotation.*;


RESTful Service

CustomerResource 

Normally a real service will be backed by JPA to do persistence operations (see:  Creating a RESTful Web Service - Part 4/5). But for this post I will use a "Hello World" style service that returns a Customer based on an ID as XML and JSON to illustrate some points about binding.

package org.example.service;

import javax.ejb.*;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import org.example.model.*;

@Stateless
@LocalBean
@Path("/customers")
public class CustomerResource {

    @GET
    @Produces({
        MediaType.APPLICATION_XML,
        MediaType.APPLICATION_JSON
    })
    @Path("{id}")
    public Customer read(@PathParam("id") int id) {
        Customer customer = new Customer();
        customer.setId(id);
        customer.setFirstName("Jane");
        customer.setLastName(null);
        
        PhoneNumber pn = new PhoneNumber();
        pn.setType("work");
        pn.setValue("5551111");
        customer.getPhoneNumbers().add(pn);
        
        return customer;
     }

}

CustomerApplication 

MOXy is configured as the JSON-binding provider using the MOXyJsonProvider class via a JAX-RS Application class (see: MOXy as your JAX-RS JSON Provider - MOXyJsonProvider).  MOXyJsonProvider offers different settings you can use to customize the JSON representation.  In this example we will leverage the wrapperAsArrayName property to clean up the representation of collections (see:  Binding to JSON & XML - Handling Collections).

package org.example.service;

import java.util.*;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
 
@ApplicationPath("rest/*")
public class CustomerApplication  extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        HashSet<Class<?>> set = new HashSet<Class<?>>(1);
        set.add(CustomerResource.class);
        return set;
    }
 
    @Override
    public Set<Object> getSingletons() {
        MOXyJsonProvider moxyJsonProvider = new MOXyJsonProvider();
        moxyJsonProvider.setWrapperAsArrayName(true);
 
        HashSet<Object> set = new HashSet<Object>(1);
        set.add(moxyJsonProvider);
        return set;
    }

}


Output

We will examine the output we get from calling our service with the following URL using the application/xml and application/json media types.

http://localhost:7001/CustomerResource/rest/customers/1

XML

Below is a sample of the XML output.  It isn't a surprise as it exactly matches the JAXB and MOXy metadata that we applied to our model.

<?xml version="1.0" encoding="UTF-8"?>
<customer 
    xmlns="http://www.example.org/model" id="1">
    <personalInfo>
        <firstName>Jane</firstName>
        <lastName 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
    </personalInfo>
    <phoneNumbers>
        <phoneNumber type="work">5551111</phoneNumber>
    </phoneNumbers>
</customer>

Note the following XML specific things about this output.
  1. There is a root element (line 2).
  2. The XML is namespace qualified. (line 3).
  3. There are XML attributes (lines 3 & 10).
  4. The id attribute contains a int value (line 3).
  5. The xsi:nil attribute is used to indicate that the lastName element contains a null value. (lines 6 & 7).
  6. There are a collection of phoneNumber elements with a grouping element called phoneNumbers (lines 9-11).

JSON

Below is the JSON response when MOXy's JSON-binding is used by leveraging MOXyJsonProvider.  It was produced using the exact same metadata as the XML representation, but all the XML specific items are gone and JSON specific items are used instead.

{
    "id": 1,
    "personalInfo": {
        "firstName": "Jane",
        "lastName": null
    },
    "phoneNumbers": [
        {
            "type": "work",
            "value": "5551111"
        }
    ]
}

Now lets compare the JSON output with what WebLogic would have produced by default.  By default WebLogic uses its JAXB implementation with an intermediate library to convert XML events to/from JSON.   Since MOXy is the default JAXB implementation in WebLogic the @XmlPath annotation is applied, but we don't get any of the other benefits from MOXy's JSON-binding.

{
    "@id": "1",
    "personalInfo": {
        "firstName": "Jane",
        "lastName": {
            "@nil": "true"
        }
    },
    "phoneNumbers": {
        "phoneNumber": {
            "@type": "work",
            "$": "5551111"
        }
    }
}

Below are some of the problems that we see with the default representation that weren't present when we used MOXy's JSON-binding.  They are all due to the XML representation leaking into the JSON representation.
  1. Keys that correspond to properties mapped with @XmlAttribute are prefixed with @ (lines 2 & 11).
  2. The int value for the id property is incorrectly written as a JSON string (line 2).
  3. JSON representation of null is not used for the lastName key (lines 5-7).
  4. Our List of PhoneNumber objects were not marshalled correctly as a JSON array of size 1 (lines 10-13).
  5. Our phoneNumbers property will map to a JSON key of phoneNumber instead of phoneNumbers (line 10).

Further Reading

If you enjoyed this post then you may also be interested in:

No comments:

Post a Comment