Jump to: navigation, search

Difference between revisions of "EclipseLink/Release/2.4.0/JSONBinding"

(Object-to-JSON Binding)
 
(45 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
<div style="margin:5px;float:right;border:1px solid #000000;padding:5px">__TOC__</div>  
 
<div style="margin:5px;float:right;border:1px solid #000000;padding:5px">__TOC__</div>  
  
=Object-to-JSON Binding Layer=
+
=Object-to-JSON Binding =
  
==Overview==
+
New in EclipseLink 2.4 is support for converting objects directly to and from JSON.  This can be useful when creating RESTful services, as JAX-RS services often accept both XML ('''application/xml''') and JSON ('''application/json''') messages.
 
+
New in EclipseLink 2.4 is support for converting objects to and from JSON.  This can be useful when creating RESTful services, as JAX-RS services often accept both XML ('''application/xml''') and JSON ('''application/json''') messages.
+
  
 +
=Overview=
  
 
'''Same Flexibility as Object-to-XML Mapping'''
 
'''Same Flexibility as Object-to-XML Mapping'''
  
JSON binding is compatible with most existing MOXy extensions. This includes:
+
Everything from MOXy's object-to-XML mapping technology is available to you to read and write JSON. This includes:
  
*External bindings file
+
* Advanced mapping functionality above and beyond the JAXB specification
*Dynamic JAXB
+
* Ability to store mappings in External Bindings files
*Extensible models
+
* Dynamic models using Dynamic JAXB
 +
* Extensible models supporting multi-tenancy applications
  
  
Line 52: Line 52:
 
   private int id;
 
   private int id;
  
  @XmlElement
 
 
   private String bar;
 
   private String bar;
  
Line 60: Line 59:
  
  
'''No Additional Compile-Time Dependencies'''
+
'''No Additional Dependencies'''
  
JSON binding does not require any additional compile-time dependencies above and beyond what is required for normal JAXB usage.
+
JSON binding does not require any additional dependencies above and beyond what is required for standard JAXB usage.
  
  
 
'''Easy to Use with JAX-RS'''
 
'''Easy to Use with JAX-RS'''
  
EclipseLink JSON support makes it easy to implement '''MessageBodyReader''' and '''MessageBodyWriter''' classes for use with RESTful web services.
+
EclipseLink can easily work as a JSON provider for JAX-RS web services using the included '''MOXyJsonProvider''', without the need to implement your own <tt>MessageBodyReader/Writer</tt> classes:
  
 +
<div style="width:850px">
 +
<source lang="java">
 +
package org.example;
 +
 +
import java.util.*;
 +
import javax.ws.rs.core.Application;
 +
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
 +
 +
public class CustomerApplication extends Application {
 +
 +
    @Override
 +
    public Set<Class<?>> getClasses() {
 +
        HashSet<Class<?>> set = new HashSet<Class<?>>(2);
 +
        set.add(MOXyJsonProvider.class);
 +
        set.add(CustomerService.class);
 +
        return set;
 +
    }
 +
 +
}
 +
</source>
 +
</div>
  
==Marshalling and Unmarshalling with JSON==
+
=Marshalling and Unmarshalling with JSON=
  
Adapting your application to produce and consume JSON is as easy as setting a property on your JAXB '''Marshaller''' or '''Unmarshaller''':
+
Adapting your application to produce and consume JSON is as easy as setting an '''eclipselink.media-type''' property on your JAXB '''Marshaller''' or '''Unmarshaller''':
  
 
<div style="width:850px">
 
<div style="width:850px">
 
<source lang="java">
 
<source lang="java">
import org.eclipse.persistence.jaxb.MarshallerProperties;
 
import org.eclipse.persistence.oxm.MediaType;
 
 
 
...
 
...
  
 
Marshaller m = jaxbContext.createMarshaller();
 
Marshaller m = jaxbContext.createMarshaller();
m.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
+
m.setProperty("eclipselink.media-type", "application/json");
  
 
Unmarshaller u = jaxbContext.createUnmarshaller();
 
Unmarshaller u = jaxbContext.createUnmarshaller();
u.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
+
u.setProperty("eclipselink.media-type", "application/json");
  
 
...
 
...
Line 91: Line 108:
 
</div>
 
</div>
  
The '''MEDIA_TYPE''' property can also be specified in the Map of properties used during creation of the '''JAXBContext'''.  In this case, Marshallers and Unmarshallers created from that context will automatically use the specified media type:
+
The following constants are also available:
  
 
<div style="width:850px">
 
<div style="width:850px">
 
<source lang="java">
 
<source lang="java">
 +
import org.eclipse.persistence.jaxb.MarshallerProperties;
 +
import org.eclipse.persistence.jaxb.UnarshallerProperties;
 +
import org.eclipse.persistence.oxm.MediaType;
 +
 +
m.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
 +
u.setProperty(UnarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
 +
</source>
 +
</div>
 +
 +
The '''eclipselink.media-type''' property can also be specified in the Map of properties used during creation of the '''JAXBContext'''.  In this case, Marshallers and Unmarshallers created from that context will automatically use the specified media type:
 +
 +
<div style="width:850px">
 +
<source lang="java">
 +
import org.eclipse.persistence.jaxb.JAXBContextProperties;
 +
import org.eclipse.persistence.oxm.MediaType;
 +
 
Map<String, Object> properties = new HashMap<String, Object>();
 
Map<String, Object> properties = new HashMap<String, Object>();
 
properties.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
 
properties.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
Line 105: Line 138:
  
  
== JSON Data Types ==
+
=Example: Basic JSON Binding=
 +
 
 +
Below is an example of a JSON binding that does not require any additional compile time dependencies above and beyond what is required for normal JAXB usage.  It unmarshals JSON from a '''StreamSource''' into a user-object '''SearchResults''', adds a new '''Result''' to the collection, and marshals the new collection to System.out:
 +
 
 +
<div style="width:850px">
 +
<source lang="java">
 +
package org.example;
 +
 
 +
import org.example.model.Result;
 +
import org.example.model.SearchResults;
 +
 
 +
import java.util.Date;
 +
 +
import javax.xml.bind.JAXBContext;
 +
import javax.xml.bind.JAXBElement;
 +
import javax.xml.bind.Marshaller;
 +
import javax.xml.bind.Unmarshaller;
 +
import javax.xml.transform.stream.StreamSource;
 +
 +
public class Demo {
 +
 +
    public static void main(String[] args) throws Exception {
 +
        JAXBContext jc = JAXBContext.newInstance(SearchResults.class);
 +
 +
        Unmarshaller unmarshaller = jc.createUnmarshaller();
 +
        unmarshaller.setProperty("eclipselink.media-type", "application/json");
 +
        StreamSource source = new StreamSource("http://search.twitter.com/search.json?q=jaxb");
 +
        JAXBElement<SearchResults> jaxbElement = unmarshaller.unmarshal(source, SearchResults.class);
 +
 +
        Result result = new Result();
 +
        result.setCreatedAt(new Date());
 +
        result.setFromUser("bsmith");
 +
        result.setText("You can now use EclipseLink JAXB (MOXy) with JSON :)");
 +
        jaxbElement.getValue().getResults().add(result);
 +
 +
        Marshaller marshaller = jc.createMarshaller();
 +
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
 +
        marshaller.setProperty("eclipselink.media-type", "application/json");
 +
        marshaller.marshal(jaxbElement, System.out);
 +
    }
 +
 +
}
 +
</source>
 +
</div>
 +
 
 +
 
 +
= External Bindings =
 +
 
 +
MOXy External Bindings files can now be written in JSON.  Below is an example '''bindings.json''' that maps <tt>Customer</tt> and <tt>PhoneNumber</tt> classes to JSON:
 +
 
 +
<div style="width:850px">
 +
<source lang="xml">
 +
{
 +
  "package-name" : "org.example",
 +
  "xml-schema" : {
 +
      "element-form-default" : "QUALIFIED",
 +
      "namespace" : "http://www.example.com/customer"
 +
  },
 +
  "java-types" : {
 +
      "java-type" : [ {
 +
        "name" : "Customer",
 +
        "xml-type" : {
 +
            "prop-order" : "firstName lastName address phoneNumbers"
 +
        },
 +
        "xml-root-element" : {},
 +
        "java-attributes" : {
 +
            "xml-element" : [
 +
                {"java-attribute" : "firstName","name" : "first-name"},
 +
                {"java-attribute" : "lastName", "name" : "last-name"},
 +
                {"java-attribute" : "phoneNumbers","name" : "phone-number"}
 +
            ]
 +
        }
 +
      }, {
 +
        "name" : "PhoneNumber",
 +
        "java-attributes" : {
 +
            "xml-attribute" : [
 +
                {"java-attribute" : "type"}
 +
            ],
 +
            "xml-value" : [
 +
                {"java-attribute" : "number"}
 +
            ]
 +
        }
 +
      } ]
 +
  }
 +
}
 +
</source>
 +
</div>
 +
 
 +
This JSON file can then be used when bootstrapping a JAXBContext:
 +
 
 +
<div style="width:900px">
 +
<source lang="java">
 +
Map<String, Object> properties = new HashMap<String, Object>(2);
 +
properties.put("eclipselink-oxm-xml", "org/example/binding.json");
 +
properties.put("eclipselink.media-type", "application/json");
 +
JAXBContext context = JAXBContext.newInstance("org.example", Customer.class.getClassLoader() , properties);
 +
 
 +
Unmarshaller unmarshaller = context.createUnmarshaller();
 +
StreamSource json = new StreamSource(new File("src/org/example/input.json"));
 +
...
 +
</source>
 +
</div>
 +
 
 +
 
 +
= JSON Data Types =
  
 
XML has one datatype, text, whereas JSON differentiates between strings, numbers, and booleans.  MOXy supports these datatypes automatically:
 
XML has one datatype, text, whereas JSON differentiates between strings, numbers, and booleans.  MOXy supports these datatypes automatically:
Line 111: Line 248:
 
<div style="width:850px">
 
<div style="width:850px">
 
<source lang="java">
 
<source lang="java">
@XmlRootElement
 
 
public class Address {
 
public class Address {
  
  @XmlElement
 
 
   private int id;
 
   private int id;
  @XmlElement
 
 
   private String city;
 
   private String city;
  @XmlElement
 
 
   private boolean isMailingAddress;
 
   private boolean isMailingAddress;
  
Line 128: Line 261:
 
<source lang="xml">
 
<source lang="xml">
 
{
 
{
   "address": {
+
   "id" : 1,
      "id" : 1,
+
  "city" : "Ottawa",
      "city" : "Ottawa",
+
  "isMailingAddress" : true
      "isMailingAddress" : true
+
  }
+
 
}
 
}
 
</source>
 
</source>
Line 138: Line 269:
  
  
== Attributes ==
+
= Attributes =
  
 
JSON doesn't have the concept of attributes, so by default anything mapped as an '''@XmlAttribute''' will be marshalled as an element.  During unmarshal, elements will trigger both the attribute and element events, to allow either the mapped attribute or element to handle the value.  If there is an element and attribute with the same name, this will cause problems.
 
JSON doesn't have the concept of attributes, so by default anything mapped as an '''@XmlAttribute''' will be marshalled as an element.  During unmarshal, elements will trigger both the attribute and element events, to allow either the mapped attribute or element to handle the value.  If there is an element and attribute with the same name, this will cause problems.
Line 174: Line 305:
  
  
==Support for No "Root Element"==
+
=Support for No "Root Element"=
  
 
JSON supports documents with no root element:
 
JSON supports documents with no root element:
Line 199: Line 330:
 
</source>
 
</source>
 
</div>
 
</div>
 +
  
 
'''Marshal'''
 
'''Marshal'''
Line 209: Line 341:
 
</source>
 
</source>
 
</div>
 
</div>
 +
  
 
'''Unmarshal'''
 
'''Unmarshal'''
Line 222: Line 355:
  
  
==Collections==
+
=Namespaces=
 +
 
 +
JSON doesn't have the concept of namespaces, and by default namespaces/prefixes will be ignored during marshal and unmarshal operations.  This default behavior could be a problem if there are multiple mappings with the same local name in different namespaces as there would be no way to distinguish between those mappings.
 +
 
 +
In this case, the user can supply Map of '''namespace->prefix''' (or an instance of '''NamespacePrefixMapper''') to the Marshaller and Unmarshaller, and namespace prefixes will appear in the marshalled document, prepended to the element name.  This prefix will be recognized during unmarshal, and the resulting Java objects will be placed in the proper namespaces.
 +
 
 +
<div style="width:850px">
 +
<source lang="java">
 +
Map<String, String> namespaces = new HashMap<String, String>();
 +
namespaces.put("namespace1", "ns1");
 +
namespaces.put("namespace2", "ns2");
 +
jsonMarshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);
 +
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespaces);
 +
</source>
 +
</div>
 +
 
 +
Note that '''MarshallerProperties.NAMESPACE_PREFIX_MAPPER''' applies to both XML and JSON, whereas '''UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER''' is a JSON-only property; XML unmarshalling can obtain the namespace information from the document itself.
 +
 
 +
When JSON is marshalled, the namespaces will be given the prefix from the Map separated by a dot:
 +
 
 +
<div style="width:850px">
 +
<source lang="xml">
 +
{
 +
  "ns1.employee : {
 +
      "ns2.id" : 123
 +
  }
 +
}
 +
</source>
 +
</div>
 +
 
 +
The dot separator can be set to a custom Character using the '''JSON_NAMESPACE_SEPARATOR''' property:
 +
 
 +
<div style="width:850px">
 +
<source lang="java">
 +
jsonMarshaller.setProperty(MarshallerProperties.JSON_NAMESPACE_SEPARATOR, ':');
 +
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_SEPARATOR, ':');
 +
</source>
 +
</div>
 +
 
 +
 
 +
=Inheritance=
 +
 
 +
Inheritance is supported when marshalling to JSON.  The following example shows a marshalled XML document using inheritance, as well as the equivalent JSON document:
 +
 
 +
<div style="width:850px">
 +
<source lang="xml">
 +
<?xml version="1.0" encoding="UTF-8"?>
 +
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="employee">
 +
  <name>Bob Smith</name>
 +
</person>
 +
</source>
 +
</div>
 +
 
 +
<div style="width:850px">
 +
<source lang="xml">
 +
{
 +
  "person" : {
 +
      "type" : "employee",
 +
      "name" : "Bob Smith"
 +
  }
 +
}
 +
</source>
 +
</div>
 +
 
 +
 
 +
=Null Handling=
 +
 
 +
Behaviour when marshalling null values can be configured using the '''nillable''' attribute of the '''@XmlElement''' annotation.  By default, null values will not be marshalled to JSON:
 +
 
 +
<div style="width:850px">
 +
<source lang="java">
 +
@XmlRootElement
 +
public class Employee {
 +
 
 +
  private String name = "Bob Smith";
 +
  private Address address = null;
 +
 
 +
}
 +
</source>
 +
</div>
 +
 
 +
<div style="width:850px">
 +
<source lang="xml">
 +
{
 +
  "employee" : {
 +
      "name" : "Bob Smith"
 +
  }
 +
}
 +
</source>
 +
</div>
 +
 
 +
Using '''@XmlElement(nillable = true)''' will produce an empty element:
 +
 
 +
<div style="width:850px">
 +
<source lang="java">
 +
...
 +
  @XmlElement(nillable = true)
 +
  private Address address = null;
 +
...
 +
</source>
 +
</div>
 +
 
 +
<div style="width:850px">
 +
<source lang="xml">
 +
{
 +
  "employee" : {
 +
      "name" : "Bob Smith"
 +
      "address" : null
 +
  }
 +
}
 +
</source>
 +
</div>
 +
 
 +
 
 +
=Collections=
  
 
By default, when marshalling to JSON, empty collections are marshalled as '''[ ]''':
 
By default, when marshalling to JSON, empty collections are marshalled as '''[ ]''':
Line 252: Line 499:
 
</source>
 
</source>
 
</div>
 
</div>
 +
  
 
'''Root-Level Collections'''
 
'''Root-Level Collections'''
Line 306: Line 554:
  
  
==Namespaces==
+
=XmlValue=
  
JSON doesn't have the concept of namespaces, and by default namespaces/prefixes will be ignored during marshal and unmarshal operations.  This default behavior could be a problem if there are multiple mappings with the same local name in different namespaces as there would be no way to distinguish between those mappings.
+
Using the following example classes:
 
+
In this case, the user can supply Map of '''namespace->prefix''' (or an instance of '''NamespacePrefixMapper''') to the Marshaller and Unmarshaller, and namespace prefixes will appear in the marshalled document, prepended to the element name.  This prefix will be recognized during unmarshal, and the resulting Java objects will be placed in the proper namespaces.
+
  
 
<div style="width:850px">
 
<div style="width:850px">
 
<source lang="java">
 
<source lang="java">
Map<String, String> namespaces = new HashMap<String, String>();
+
@XmlRootElement
namespaces.put("namespace1", "ns1");
+
public class Employee {
namespaces.put("namespace2", "ns2");
+
 
jsonMarshaller.setProperty(MarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespaces);
+
  public String name;
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespaces);
+
 
 +
  public Phone mainPhone;
 +
 
 +
  public List<Phone> otherPhones = new ArrayList<Phone>();
 +
 
 +
}
 +
 
 +
public class Phone {
 +
 
 +
  @XmlValue
 +
  public String number;
 +
 +
  public Phone() {
 +
      this("");
 +
  }
 +
 +
  public Phone(String num) {
 +
      this.number = num;
 +
  }
 +
 
 +
}
 
</source>
 
</source>
 
</div>
 
</div>
  
The namespaces will be given the prefix from the Map separated by a dot:
+
By default, properties annotated with '''@XmlValue''' will appear as follows when marshalled to JSON:
  
 
<div style="width:850px">
 
<div style="width:850px">
 
<source lang="xml">
 
<source lang="xml">
 
{
 
{
   "ns1.employee : {
+
   "employee" : {  
       "ns2.id" : 123
+
       "name" : "Bob Smith",
 +
      "mainPhone" : "555-5555",
 +
      "otherPhones" : [ "123-1234", "345-3456" ]
 
   }
 
   }
 
}
 
}
Line 334: Line 602:
 
</div>
 
</div>
  
Note that '''NAMESPACE_PREFIX_MAPPER''' only comes into play during unmarshal if the media type is set to JSON; XML unmarshalling can obtain the namespace information from the document itself.
 
  
 +
'''With @XmlAttribute'''
  
==Inheritance==
+
Modifying the '''Phone''' class from the previous example:
  
Inheritance is supported when marshalling to JSON. The following example shows a marshalled XML document using inheritance, as well as the equivalent JSON document:
+
<div style="width:850px">
 +
<source lang="java">
 +
public class Phone {
 +
 
 +
  @XmlValue
 +
  public String number;
 +
 +
  @XmlAttribute
 +
  public String areaCode;
 +
 +
  public Phone() {
 +
      this("", "");
 +
  }
 +
 +
  public Phone(String num, String code) {
 +
      this.number = num;
 +
      this.areaCode = code;
 +
  }
 +
 
 +
}
 +
</source>
 +
</div>
 +
 
 +
Since in JAXB, '''@XmlValue''' classes are permitted to have one or more '''@XmlAttribute'''s, MOXy will introduce a '''value''' wrapper (otherwise, invalid JSON would be produced):
  
 
<div style="width:850px">
 
<div style="width:850px">
 
<source lang="xml">
 
<source lang="xml">
<?xml version="1.0" encoding="UTF-8"?>
+
{
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="employee">
+
  "employee" : {
  <name>Bob Smith</name>
+
      "name" : "Bob Smith",
</person>
+
      "mainPhone" : {
 +
        "areaCode" : "613",
 +
        "value" : "555-5555"
 +
      },
 +
      "otherPhones" : [ {
 +
        "areaCode" : "613",
 +
        "value" : "123-1234"
 +
      }, {
 +
        "areaCode" : "613",
 +
        "value" : "345-3456"
 +
      } ]
 +
  }
 +
}
 
</source>
 
</source>
 
</div>
 
</div>
 +
 +
The '''JSON_VALUE_WRAPPER''' property can also be used to customize the value wrapper name.  For example, the BadgerFish JSON representation uses '''$''' to wrap values:
 +
 +
<div style="width:850px">
 +
<source lang="java">
 +
jsonMarshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, "$");
 +
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_VALUE_WRAPPER, "$");
 +
</source>
 +
</div>
 +
 +
This would result in:
  
 
<div style="width:850px">
 
<div style="width:850px">
 
<source lang="xml">
 
<source lang="xml">
 
{
 
{
   "person" : {
+
   "employee" : {  
       "type" : "employee",
+
       "name" : "Bob Smith",
       "name" : "Bob Smith"
+
       "mainPhone" : {
 +
        "areaCode" : "613",
 +
        "$" : "555-5555"
 +
      },
 +
      "otherPhones" : [ {
 +
        "areaCode" : "613",
 +
        "$" : "123-1234"
 +
      }, {
 +
        "areaCode" : "613",
 +
        "$" : "345-3456"
 +
      } ]
 
   }
 
   }
 
}
 
}
Line 361: Line 685:
 
</div>
 
</div>
  
 +
The '''JSON_VALUE_WRAPPER''' property can also be specified in the Map of properties used during creation of the '''JAXBContext'''.  In this case, Marshallers and Unmarshallers created from that context will automatically use the specified value wrapper:
  
==Unsupported MOXy/JAXB Annotations==
+
<div style="width:850px">
 +
<source lang="java">
 +
Map<String, Object> properties = new HashMap<String, Object>();
 +
properties.put(JAXBContextProperties.JSON_VALUE_WRAPPER, "$");
  
The following annotations are not applicable to JSON:
+
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Employee.class }, properties);
 
+
Marshaller jsonMarshaller = ctx.createMarshaller();
{| border="0" cellpadding="5" style="background:#f9f9f9; border:1px dashed #2f6fab"
+
Unmarshaller jsonUnmarshaller = ctx.createUnmarshaller();
|-
+
</source>
!XmlAttributeRef
+
</div>
!XmlMimeType
+
!XmlMixed
+
!XmlNs
+
|-
+
!XmlNsForm
+
!XmlSchema
+
!XmlProperties
+
!XmlProperty
+
|-
+
!XmlVirtualAccessMethods
+
!XmlVirtualAccessMethodsSchema
+
!XmlIsSetNullPolicy
+
!XmlJoinNodes
+
|-
+
!XmlMarshalNullRepresentation
+
!XmlNullPolicy
+
!XmlParameter
+
!XmlPath
+
|-
+
!XmlPaths
+
!XmlCDATA
+
|}
+

Latest revision as of 09:01, 26 June 2012

Object-to-JSON Binding

New in EclipseLink 2.4 is support for converting objects directly to and from JSON. This can be useful when creating RESTful services, as JAX-RS services often accept both XML (application/xml) and JSON (application/json) messages.

Overview

Same Flexibility as Object-to-XML Mapping

Everything from MOXy's object-to-XML mapping technology is available to you to read and write JSON. This includes:

  • Advanced mapping functionality above and beyond the JAXB specification
  • Ability to store mappings in External Bindings files
  • Dynamic models using Dynamic JAXB
  • Extensible models supporting multi-tenancy applications


XML and JSON Messages with One Set of Mappings

Both an XML message such as:

<foo id="123">
   <bar>Hello World</bar>
</foo>

and a JSON message such as:

{
   "foo" : {
      "id" : 123,
      "bar : "Hello World"
   }
}

can be supported by a single object model:

@XmlRootElement
public class Foo {
 
   @XmlAttribute
   private int id;
 
   private String bar;
 
}


No Additional Dependencies

JSON binding does not require any additional dependencies above and beyond what is required for standard JAXB usage.


Easy to Use with JAX-RS

EclipseLink can easily work as a JSON provider for JAX-RS web services using the included MOXyJsonProvider, without the need to implement your own MessageBodyReader/Writer classes:

package org.example;
 
import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
 
public class CustomerApplication extends Application {
 
    @Override
    public Set<Class<?>> getClasses() {
        HashSet<Class<?>> set = new HashSet<Class<?>>(2);
        set.add(MOXyJsonProvider.class);
        set.add(CustomerService.class);
        return set;
    }
 
}

Marshalling and Unmarshalling with JSON

Adapting your application to produce and consume JSON is as easy as setting an eclipselink.media-type property on your JAXB Marshaller or Unmarshaller:

...
 
Marshaller m = jaxbContext.createMarshaller();
m.setProperty("eclipselink.media-type", "application/json");
 
Unmarshaller u = jaxbContext.createUnmarshaller();
u.setProperty("eclipselink.media-type", "application/json");
 
...

The following constants are also available:

import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.UnarshallerProperties;
import org.eclipse.persistence.oxm.MediaType;
 
m.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
u.setProperty(UnarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);

The eclipselink.media-type property can also be specified in the Map of properties used during creation of the JAXBContext. In this case, Marshallers and Unmarshallers created from that context will automatically use the specified media type:

import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.oxm.MediaType;
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
 
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Employee.class }, properties);
Marshaller jsonMarshaller = ctx.createMarshaller();
Unmarshaller jsonUnmarshaller = ctx.createUnmarshaller();


Example: Basic JSON Binding

Below is an example of a JSON binding that does not require any additional compile time dependencies above and beyond what is required for normal JAXB usage. It unmarshals JSON from a StreamSource into a user-object SearchResults, adds a new Result to the collection, and marshals the new collection to System.out:

package org.example;
 
import org.example.model.Result; 
import org.example.model.SearchResults;
 
import java.util.Date;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
 
public class Demo {
 
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(SearchResults.class);
 
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setProperty("eclipselink.media-type", "application/json");
        StreamSource source = new StreamSource("http://search.twitter.com/search.json?q=jaxb");
        JAXBElement<SearchResults> jaxbElement = unmarshaller.unmarshal(source, SearchResults.class);
 
        Result result = new Result();
        result.setCreatedAt(new Date());
        result.setFromUser("bsmith");
        result.setText("You can now use EclipseLink JAXB (MOXy) with JSON :)");
        jaxbElement.getValue().getResults().add(result);
 
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty("eclipselink.media-type", "application/json");
        marshaller.marshal(jaxbElement, System.out);
    }
 
}


External Bindings

MOXy External Bindings files can now be written in JSON. Below is an example bindings.json that maps Customer and PhoneNumber classes to JSON:

{
   "package-name" : "org.example",
   "xml-schema" : {
      "element-form-default" : "QUALIFIED",
      "namespace" : "http://www.example.com/customer"
   },
   "java-types" : {
      "java-type" : [ {
         "name" : "Customer",
         "xml-type" : {
            "prop-order" : "firstName lastName address phoneNumbers"
         },
         "xml-root-element" : {},
         "java-attributes" : {
            "xml-element" : [ 
                {"java-attribute" : "firstName","name" : "first-name"}, 
                {"java-attribute" : "lastName", "name" : "last-name"}, 
                {"java-attribute" : "phoneNumbers","name" : "phone-number"}
            ]
         }
      }, {
         "name" : "PhoneNumber",
         "java-attributes" : {
            "xml-attribute" : [ 
                {"java-attribute" : "type"}
            ],
            "xml-value" : [ 
                {"java-attribute" : "number"}
            ]
         }
      } ]
   }
}

This JSON file can then be used when bootstrapping a JAXBContext:

Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put("eclipselink-oxm-xml", "org/example/binding.json");
properties.put("eclipselink.media-type", "application/json");
JAXBContext context = JAXBContext.newInstance("org.example", Customer.class.getClassLoader() , properties);
 
Unmarshaller unmarshaller = context.createUnmarshaller();
StreamSource json = new StreamSource(new File("src/org/example/input.json"));
...


JSON Data Types

XML has one datatype, text, whereas JSON differentiates between strings, numbers, and booleans. MOXy supports these datatypes automatically:

public class Address {
 
   private int id;
   private String city;
   private boolean isMailingAddress;
 
}
{
   "id" : 1,
   "city" : "Ottawa",
   "isMailingAddress" : true
}


Attributes

JSON doesn't have the concept of attributes, so by default anything mapped as an @XmlAttribute will be marshalled as an element. During unmarshal, elements will trigger both the attribute and element events, to allow either the mapped attribute or element to handle the value. If there is an element and attribute with the same name, this will cause problems.

Users will be able to override the default behavior by providing an attribute prefix, which will be prepended to the attribute name during marshal, and recognized during unmarshal. In the example below the number field is mapped as an attribute.

jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, "@");
jsonMarshaller.setProperty(MarshallerProperties.JSON_ATTRIBUTE_PREFIX, "@") ;
{
   "phone" : {
      "area-code" : "613",
      "@number" : "1234567"
   }
}

The JSON_ATTRIBUTE_PREFIX property can also be specified in the Map of properties used during creation of the JAXBContext. In this case, Marshallers and Unmarshallers created from that context will automatically use the specified prefix:

Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "@");
 
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Phone.class }, properties);


Support for No "Root Element"

JSON supports documents with no root element:

{
   "area-code" : "613",
   "number" : "1234567"
}

As opposed to the corresponding JSON with a root element "phone":

{
   "phone": {
      "area-code" : "613",
      "number" : "1234567"
   }
}


Marshal

During marshal, if no @XmlRootElement annotation is present, then the marshalled JSON document will not have a root element. If @XmlRootElement is specified, the root element can be omitted from the JSON output by setting the following property on the JAXB Marshaller:

marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);


Unmarshal

By default it will be assumed that the document has a root element. If your documents do not contain root elements, you should set the following property on your Unmarshaller. Also note that if there is no root element, you must specify the Class being unmarshalled to.

unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
JAXBElement<SearchResults> jaxbElement = unmarshaller.unmarshal(source, SearchResults.class);


Namespaces

JSON doesn't have the concept of namespaces, and by default namespaces/prefixes will be ignored during marshal and unmarshal operations. This default behavior could be a problem if there are multiple mappings with the same local name in different namespaces as there would be no way to distinguish between those mappings.

In this case, the user can supply Map of namespace->prefix (or an instance of NamespacePrefixMapper) to the Marshaller and Unmarshaller, and namespace prefixes will appear in the marshalled document, prepended to the element name. This prefix will be recognized during unmarshal, and the resulting Java objects will be placed in the proper namespaces.

Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("namespace1", "ns1");
namespaces.put("namespace2", "ns2");
jsonMarshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespaces);

Note that MarshallerProperties.NAMESPACE_PREFIX_MAPPER applies to both XML and JSON, whereas UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER is a JSON-only property; XML unmarshalling can obtain the namespace information from the document itself.

When JSON is marshalled, the namespaces will be given the prefix from the Map separated by a dot:

{
   "ns1.employee : {
      "ns2.id" : 123
   }
}

The dot separator can be set to a custom Character using the JSON_NAMESPACE_SEPARATOR property:

jsonMarshaller.setProperty(MarshallerProperties.JSON_NAMESPACE_SEPARATOR, ':');
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_SEPARATOR, ':');


Inheritance

Inheritance is supported when marshalling to JSON. The following example shows a marshalled XML document using inheritance, as well as the equivalent JSON document:

<?xml version="1.0" encoding="UTF-8"?>
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="employee">
  <name>Bob Smith</name>
</person>
{
   "person" : {
      "type" : "employee",
      "name" : "Bob Smith"
   }
}


Null Handling

Behaviour when marshalling null values can be configured using the nillable attribute of the @XmlElement annotation. By default, null values will not be marshalled to JSON:

@XmlRootElement
public class Employee {
 
   private String name = "Bob Smith";
   private Address address = null;
 
}
{
   "employee" : {
      "name" : "Bob Smith"
   }
}

Using @XmlElement(nillable = true) will produce an empty element:

...
   @XmlElement(nillable = true)
   private Address address = null;
...
{
   "employee" : {
      "name" : "Bob Smith"
      "address" : null
   }
}


Collections

By default, when marshalling to JSON, empty collections are marshalled as [ ]:

{
   "phone" : {
      "myList" : [ ]
   }
}

The JSON_MARSHAL_EMPTY_COLLECTIONS Marshaller property can be set to false to change this behavior so that empty collections are not marshalled at all:

 jsonMarshaller.setProperty(MarshallerProperties.JSON_MARSHAL_EMPTY_COLLECTIONS, Boolean.FALSE) ;
{
   "phone" : {
   }
}


Root-Level Collections

JSON documents can have root level lists. If a class Root has an @XmlRootElement(name="root")' specified, we could marshal as follows:

marshaller.marshal(myListOfRoots, System.out);
[ {
   "root" : {
      "name" : "aaa"
   }
}, {
   "root" : {
      "name" : "bbb"
   }
} ]

Since the root element is present in the document, we can unmarshal using:

unmarshaller.unmarshal(json);

If Root does not have an @XmlRootElement, or if JSON_INCLUDE_ROOT has been set to false, the marshal would give this output:

[ {
   "name":"aaa"
}, {
   "name":"bbb"
} ]

Since the root element is not present, we would need to provide the class to unmarshal to:

unmarshaller.unmarshal(json, Root.class);


XmlValue

Using the following example classes:

@XmlRootElement
public class Employee {
 
   public String name;
 
   public Phone mainPhone;
 
   public List<Phone> otherPhones = new ArrayList<Phone>();
 
}
 
public class Phone {
 
   @XmlValue
   public String number;
 
   public Phone() {
      this("");
   }
 
   public Phone(String num) {
      this.number = num;
   }
 
}

By default, properties annotated with @XmlValue will appear as follows when marshalled to JSON:

{
   "employee" : { 
      "name" : "Bob Smith",
      "mainPhone" : "555-5555",
      "otherPhones" : [ "123-1234", "345-3456" ]
   }
}


With @XmlAttribute

Modifying the Phone class from the previous example:

public class Phone {
 
   @XmlValue
   public String number;
 
   @XmlAttribute
   public String areaCode;
 
   public Phone() {
      this("", "");
   }
 
   public Phone(String num, String code) {
      this.number = num;
      this.areaCode = code;
   }
 
}

Since in JAXB, @XmlValue classes are permitted to have one or more @XmlAttributes, MOXy will introduce a value wrapper (otherwise, invalid JSON would be produced):

{
   "employee" : { 
      "name" : "Bob Smith",
      "mainPhone" : {
         "areaCode" : "613",
         "value" : "555-5555"
      },
      "otherPhones" : [ {
         "areaCode" : "613",
         "value" : "123-1234"
      }, {
         "areaCode" : "613",
         "value" : "345-3456"
      } ]
   }
}

The JSON_VALUE_WRAPPER property can also be used to customize the value wrapper name. For example, the BadgerFish JSON representation uses $ to wrap values:

jsonMarshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, "$");
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_VALUE_WRAPPER, "$");

This would result in:

{
   "employee" : { 
      "name" : "Bob Smith",
      "mainPhone" : {
         "areaCode" : "613",
         "$" : "555-5555"
      },
      "otherPhones" : [ {
         "areaCode" : "613",
         "$" : "123-1234"
      }, {
         "areaCode" : "613",
         "$" : "345-3456"
      } ]
   }
}

The JSON_VALUE_WRAPPER property can also be specified in the Map of properties used during creation of the JAXBContext. In this case, Marshallers and Unmarshallers created from that context will automatically use the specified value wrapper:

Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.JSON_VALUE_WRAPPER, "$");
 
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Employee.class }, properties);
Marshaller jsonMarshaller = ctx.createMarshaller();
Unmarshaller jsonUnmarshaller = ctx.createUnmarshaller();