Skip to main content

Notice: This Wiki is now read only and edits are no longer possible. Please see: for the plan.

Jump to: navigation, search


< EclipseLink‎ | DesignDocs
Revision as of 09:31, 5 June 2012 by (Talk | contribs) (No "Root Element" Support)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Design Specification: Object-to-JSON Binding Layer

ER 350483

Document History

Date Author Version Description & Notes
 ? Denise Smith  ?
2011/08/11 Blaise Doughan  ?

Project overview

This feature will add support for converting objects to/from JSON. This is desirable when creating RESTful services as JAX-RS services often accept both XML (application/xml) and JSON (application/json) messages.


  • Offer the same flexibility as our object-to-XML mappings
  • Where services offer both XML and JSON messages, support both with one set of mappings
  • Not require additional compile time dependencies over the JAXB APIs
  • Be easy to use with JAX-RS (i.e. MessageBodyReader and MessageBodyWriter)


See for more JSON reference


1 - Same Flexibility as Our Object-to-XML Mapping

The new JSON binding will be compatible with all existing MOXy extensions. This includes:

  • External bindings file
  • Dynamic JAXB
  • Extensible models

2 - Where Services Offer Both XML and JSON Messages, Support Both with one set of mappings

If the XML representation is:

<foo xmlns="urn:examle" id="123">
   <bar>Hello World</bar>

And the JSON representation is:

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

The goal is to have this supported by one object model:

public class Foo {
   private int id;
   private String bar;

3 - Not Require Additional Compile Time Dependencies over the JAXB APIs

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 (which is none when using Java SE 6).

package blog.json.twitter;
import java.util.Date;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Demo {
    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(SearchResults.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setProperty("", "application/json");
        StreamSource source = new StreamSource("");
        JAXBElement<SearchResults> jaxbElement = unmarshaller.unmarshal(source, SearchResults.class);
        Result result = new Result();
        result.setCreatedAt(new Date());
        result.setText("You can now use EclipseLink JAXB (MOXy) with JSON :)");
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty("", "application/json");
        marshaller.marshal(jaxbElement, System.out);

4 - Easy to Use with JAX-RS (i.e. MessageBodyReader and MessageBodyWriter)

The following two JAX-RS classes are one way that users could interact with MOXy's JSON binding. Below is a demonstration of what the implementations could look like:

package org.example;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import javax.xml.bind.*;
public class MOXyJSONProvider implements
    MessageBodyReader<Object>, MessageBodyWriter<Object>{
    protected Providers providers;
    public boolean isReadable(Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType) {
        return true;
    public Object readFrom(Class<Object> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
            throws IOException, WebApplicationException {
            try {
                Class<?> domainClass = getDomainClass(genericType);
                Unmarshaller u = getJAXBContext(domainClass, mediaType).createUnmarshaller();
                u.setProperty("", mediaType.toString());
                u.setProperty("eclipselink.json.include-root", false);
                return u.unmarshal(new StreamSource(entityStream), domainClass).getValue();
            } catch(JAXBException jaxbException) {
                throw new WebApplicationException(jaxbException);
    public boolean isWriteable(Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType) {
        return true;
    public void writeTo(Object object, Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType,
        MultivaluedMap<String, Object> httpHeaders,
        OutputStream entityStream) throws IOException,
        WebApplicationException {
        try {
            Class<?> domainClass = getDomainClass(genericType);
            Marshaller m = getJAXBContext(domainClass, mediaType).createMarshaller();
            m.setProperty("", mediaType.toString());
            m.setProperty("eclipselink.json.include-root", false);
            m.marshal(object, entityStream);
        } catch(JAXBException jaxbException) {
            throw new WebApplicationException(jaxbException);
    public long getSize(Object t, Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType) {
        return -1;
    private JAXBContext getJAXBContext(Class<?> type, MediaType mediaType)
        throws JAXBException {
        ContextResolver<JAXBContext> resolver
            = providers.getContextResolver(JAXBContext.class, mediaType);
        JAXBContext jaxbContext;
        if(null == resolver || null == (jaxbContext = resolver.getContext(type))) {
            return JAXBContext.newInstance(type);
        } else {
            return jaxbContext;
    private Class<?> getDomainClass(Type genericType) {
        if(genericType instanceof Class) {
            return (Class<?>) genericType;
        } else if(genericType instanceof ParameterizedType) {
            return (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
        } else {
            return null;

Design Constraints

JAXB Annotations

We will leverage the existing JAXB annotations to specify a JSON binding. This will involve us coming up with JSON interpretations of the XML bindings.

Should Just Work

The following annotations should just work with our JSON binding layer, only requiring test cases to be written to confirm this.

Annotation Tested
XmlAccessorOrder Yes
XmlAccessorType Yes
XmlJavaTypeAdapter Yes
XmlJavaTypeAdapters Yes
XmlSeeAlso Yes
XmlTransient Yes
XmlType Yes

Applicable to JSON but Require Work

The following annotations will require changes to the OXM layer in order to support.

Annotation Tested
XmlAnyAttribute Yes
XmlAnyElement Yes
XmlAttribute Yes
XmlElement Yes
XmlElementWrapper Yes
XmlID Yes
XmlList Yes
XmlRootElement Yes
XmlValue Yes

Not Applicable to JSON

The following annotations are not applicable to JSON, but may still require some work so that they are ignored or throw exceptions appropriately.

Annotation Tested
XmlAttributeRef No
XmlMimeType No
XmlMixed No
XmlNs No
XmlNsForm No
XmlSchema No

MOXy Annotations

Should Just Work

The following annotations should just work with our JSON binding layer, only requiring test cases to be written to confirm this.

Annotation Tested
XmlAccessMethods Yes
XmlContainerPolicy Yes
XmlCustomizer Yes
XmlInverseReference Yes
XmlNameTransformer Yes
XmlReadOnly Yes
XmlReadTransformer Yes
XmlTransformation Yes
XmlWriteOnly Yes
XmlWriteTransformer Yes
XmlWriteTransformers Yes

Applicable to JSON but Requires Work

The following annotations will require changes to the OXM layer in order to support.

Annotation Tested
XmlClassExtractor Yes
XMLDiscriminatorNode Yes
XMLDiscriminatorValue Yes
XmlElementsJoinNodes Yes
XmlInlineBinaryData Yes
XmlJoinNode Yes
XmlKey Yes

Not Applicable to JSON

The following annotations are not applicable to JSON, but may still require some work so that they are ignored or throw exceptions appropriately.

Annotation Tested

Design / Functionality

High Level Design


We will implement a JSONReader that will convert JSON input (InputStream, Reader, System ID) into SAX events.


We will implement two new records JSONWriterRecord and JSONFormattedWriterRecord that will handle the JSON output.

Low Level Design

No "Root Element" Support

Bug 353938

JSON supports documents with no root element:


Instead of the corresponding JSON with a root element "phone"



During marshal if there is no @XmlRootElement specified then the JSON document won't have a root element (as above). If the object being marshalled does have an @XmlRootElement specified then there is the option of marshalling the root or not. By default this is true and to change this behaviour the following property can be set on the marshaller

marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);


During an unmarshal operation for the unmarshal operations that do no specify a class to unmarshal to we will always assume there is a root element in the document. For unmarshal operations that specify a class to unmarshal to there is the option to unmarshal documents that have roots and documents that don`t have roots. By default it will be assumed that the document has a root element. This can be changed by setting the following property on the unmarshaller.

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

JSON Data Types Bug 351119

XML has one datatype text, while JSON has: string, number, boolean. The OXM layer will need to be enhanced to know the type of "text" being marshalled.



JSON doesn't have the concept as attributes so by default when marshaling anything mapped as an attribute 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. Additionally there would likely be issues if an AnyAttribute or Any existed as all items would probably be duplicated in the AnyAttribute mapping and the Any Mapping.

Users will be able to override the default behaviors by providing a prefix to marshal with attributes and to recognize during unmarshal. In the example below the number attribute is mapped as an attribute. The JSON_ATTRIBUTE_PREFIX could be pass in the map of properties during creation of the JAXBContext in which case Marshaller and Unmarshallers created from that context will use the specified prefix. If the JSON_ATTRIBUTE_PREFIX is set on both the properties used during context creation and marshaller/unmarshaller the one specified on the marshaller/unmarshaller will be used.

 jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, "@");
 jsonMarshaller.setProperty(MarshallerProperties.JSON_ATTRIBUTE_PREFIX, "@") ;


By default in JSON empty collections are marshalled as []


The following 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) ;

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

marshaller.marshal(myListOfRoots, System.out);

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

Since the root element is present we can unmarshal with unmarshaller.unmarshal(json);

If does not have an @XmlRootElement or if jsonMarshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROO, Boolean.False) the marshal would give this output:

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

Since the root element is not present we need to provide the class to unmarshal to: unmarshaller.unmarshal(json, Root.class);


Bug 351588 By default namespaces/prefixes will be ignored during marshal and unmarshal operations. This default behavior is 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. Users will be able to provide a Map of namespaces to customize the behavior. In JAXB users can set a JAXBMarshaller.NAMESPACE_PREFIX_MAPPER. If a NAMESPACE_PREFIX_MAPPER is set and the media type is application/json namespaces will be written out. If a NAMESPACE_PREFIX_MAPPER is not set and the media type is application/json then namespaces will not be written. To provide different behavior per media type separate jaxbmarshallers can be created. When the mediatype is application/xml then this property is only used during marshal operations. When the mediatype is application/json the NAMESPACE_PREFIX_MAPPER will also be used during unmarshal operations.

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

If the namespace map is set on the marshaller it will be used to prefix elements during the marshal operation.

 jsonMarshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);

The namespaces will be give the prefix from the map separated with a `.` ie:


Design-To allow unmarshal to ignore namespaces XPathFragment will have an isNamespaceAware methods so that uris can be ignored during equals comparisons when in JSON unmarshal mode. Additionally a new class that represents a QName will also be introduced (org.eclipse.persistence.internal.oxm.XPathQName) and it was also have an isNamespaceAware boolean. XMLContext will store descriptors based on the new XPathQNames instead of the old QName.


Where XML would marshal an xsi:type="prefix:someChild" the following would be the equivalent JSON. "type":"prefix.someChild"

Null support

Bug 351587 When marshaling if the getMarshalNullRepresentation setting on nullpolicy is ABSENT_NODE we don't write that pair to JSON. If the getMarshalNullRepresentation is NIL we should write "null" If the getMarshalNullRepresentation is EMPTY_NODE we should write "null"

Complex Object example employee.setAddress(null);


Complex Object example employee.setAddress(new Address());


Simple example address.setCity(null);


Null & Empty Lists

Given the following class:

class Employee
  List addresses;  
  List numbers;
  @XmlElementWrapper(name="joblist", nillable = true)
  List jobs;
Employee emp = new Employee();
Employee emp = new Employee();
emp.numbers = new ArrayList();
emp.addresses= new ArrayList(); new ArrayList();


2 options for XmlValue behavior (need to choose which will be the default behavior). If has 1 field called number and it is marked with @XmlValue


Single Case:
List Case:
      "phone":["123-4567", "234-5678"]

Option2- use a customizable property that users can set to wrap the @XmlValue mappings.

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

The JSON_VALUE_WRAPPER could alternatively be put in the map of properties used during creation of the JAXBContext in which case Marshallers and Unmarshallers created from that context will use the specified JSON_VALUE_WRAPPER.

Single Case
List Case:
          "value":["123-4567", "234-5678"]

XSI type attribute

ie:CompositeObjectMapping to Object.class attributes of type java.lang.Object (or Collection of Objects).

    • Equivalent XML -
     <responsibility xmlns:xsi='' xmlns:xsd='' xsi:type="xsd:string">Fix Bugs</responsibility>


Leveraging Existing Unit Tests

Where possible existing MOXy test cases will be used to test the new JSON support. The majority of tests extend JAXBTestCases that ensures the test scenarios are run against all the supported XML sources and targets. We will introduce a new class called JAXBWithJSONTestCases that will add test methods for all supported JSON sources and targets that we will convert existing test cases to extend as functionality is added.

New Unit Tests

New test cases will also be added, where possible these tests will extend JAXBWithJSONTestCases so that the equivalent XML use case is covered as well.

New Server Tests

New server tests will be added testing this functionality with JAX-RS frameworks.


MediaType - New Enum

To marshal and unmarshal JSON instead of XML within JAXB the MediaType property as described below:

org.eclipse.persistence.oxm.MediaType will be added. This class has been designed to align with the [JAX-RS (JSR-311) MediaType enum] The initial enum values are:


New MOXy Specific JAXB Property ("" - JAXBContextProperties.MEDIA_TYPE, MarshallerProperties.MEDIA_TYPE, UnmarshallerProperties.MEDIA_TYPE)

Standard JAXB APIs will be used to marshal and unmarshal. Users will need to set a property on the JAXBContext, Marshaller and/or Unmarshaller to enable JSON mode:


If the property is set when creating a JAXBContext then all Marshallers and Unmarshallers will start in JSON mode by default:

Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
JAXBContext jaxbContext = JAXBContext.newInstance(new Class[] {Root.class}, properties);


The following code will put the Unmarshaller in JSON mode. The Unmarshaller can be set back to XML mode (default) using the the property value MediaType.APPLICATION_XML:

Unmarshaller jsonUnmarshaller = jaxbContext.createUnmarshaller();
jsonUnmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);


The following code will put the Marshaller in JSON mode. The Marshaller can be set back to XML mode (default) using the the property value "application/xml":

Marshaller jsonMarshaller = jaxbContext.createMarshaller();
jsonMarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);


Config files

  • No changes are required to the current config files to support this feature.
  • With the introduction of this feature we could support exposing our config files in JSON format.


Open Issues

This section lists the open issues that are still pending that must be decided prior to fully implementing this project's requirements.

Issue # Owner Description / Notes


This section lists decisions made. These are intended to document the resolution of open issues or constraints added to the project that are important.

Issue # Description / Notes Decision
1 How should we parse JSON messages? We have decided to use ANTLR to implement a JSON parser: Bug 351113.
2 Any Mappings and Attributes Children are typically reported as both elements and attributes. To avoid duplication issues a JAXBContext.JSON_ATTRIBUTE_PREFIX must be set for support with @XmlAnyAttribute, @XmlAnyElement
3 ChoiceCollection and CollectionReference Mappings Need to sort the list before marshalling to group the like items together.

Future Considerations

During the research for this project the following items were identified as out of scope but are captured here as potential future enhancements. If agreed upon during the review process these should be logged in the bug system:

  • When a standard parsing API for JSON becomes available the MOXy APIs should be extended to support this.



Item Descripton Effort
4. Mapping collection to attribute CompositeDirectCollectionMapping to an attribute (ie: itmem/@type should work in JSON and look like


        "type":["aaa", "bbb", "ccc"]


Tests More tests with no root element. More tests with namespaces. Removed tests extending ExternalizedMetadataTestCases. Tests that don't extend JAXBWithJSONTestCases. Test formatting
Test with "value" and "type" variables that could conflict with xsi:type and value wrapper properties.
Attribute on root with no include root set doesn`t work
Test cases for Bindings file Need to test mixing and matching of JSON and XML bindings files. Bug 376618
Dynamic JAXB Need to create some Dynamic JAXB tests
Exception handling Unmarshal a JSON doc with media type = XML results in "[Fatal Error] :1:1: Content is not allowed in prolog." and Unmarshal an XML doc with media type = JSON results in line 1:0 no viable alternative at character '<'
Testcases with HashMap Our XML test cases have an ignoreOrder option so that when marshalling XML in different environments if elements from a map are returned in a different order the test still passes. The JSON test cases are a string compare so we don't have an equivalent option.
Positional XPath iE: name[2]
XmlLocation Currently not supported in JSON
Malformed JSON - Quotes Currently our parser cannot handle if property names are not surrounded with double quotes ("). There are cases where inputs contain single (') or no quotes. This may be something we want to handle.
Malformed JSON - Comments The JSON grammar does not allow for comments, however some documents contain them (C and HTML style comments). This may be something we want to handle.


Item Descripton Effort
Attributes Current attribute support are just marshalled as elements but we should provide the option to provide a prefix ie: jsonMarshaller/jsonUnmarshaller.setProperty(JAXBContext.JSON_ATTRIBUTE_PREFIX, "@");

Bug 356826

@XmlValue default behavior Support was added for @XmlValue but default behavior needs to be addressed []
Formatting Need to add tests? Sometimes closing brace is not in the right spot. Non formatted output has spaces that we may want to remove.
TypeMappingInfoTests Ensure TypeMappingInfo tests are also converted to include JSON tests
1. Inheritance For the type attribute should JSON marshal "type":"prefix:car-type" or "type":"car-type" Also "type" is too common to use as the indicator vs. the @xsi:type in XML so needs to be able to be changed to something else.
2. Mappings to Object In XML we would write out xsi:type to preserve the actual type.
3. AnyAttribute/AnyObject mappings including Mixed Duplication will likely occur since during unmarshal everything is reported as both an attribute and an element. Issues - anycollection with mixed =true. anycollection with collisions in list. ANyAttributes will only worth with Attribute prefix turned on and we will only report explicitly marked attributes as attributes (instead of reporting as both attribute and element)
5. CharacterEscapeHandler
Object with no XmlRootElement should marshal/unmarshal even if include_root not set
Server Tests No current JAXB Server Tests exist
Include_root_element default behavior Default behavior for this setting needs to be discussed.
XmlValue and XmlPath("text()") No current support. See [XmlValue]
Namespaces Current support ignores namespaces but we should add the option to allow the user to turn namespaces on and provide a map of prefix/uri pairs to the unmarshaller/marshaller.
Escape characters Test JSON escape characters are handled properly. See []
Public properties Where should those public properties live
Root level array support JSON is allowed to have an array as the root level object but there is not currently support for this. Bug 371919
XmlPath('.') []
Formatting Confirm desired formatting and add some formatted tests.
XMLValue + Attribute + JSON with no value wrapper This should throw an exception. XmlValue is allowed with XmlAttributes but if the mediatype is JSON then there must be a value wrapper specified
Number format Test JSON supported number formats. See []
Test JAXB_FRAGMENT When marshalling a JAXB_FRAGMENT the doc should not have the outer open and close braces in JSON
CollectionReference and ChoiceCollection mappings Currently will marshal id:1,id:2 instead of id:[1,2]
Inline binary mappings Handled differently than direct mappings since direct mappings end in text() or are attributes but binary data mappings don't
Mapping XMLCollectionReferenceMapping - need to sort like choice collection org.eclipse.persistence.testing.jaxb.annotations.xmlelementsjoinnodes.collection.XmlElementsJoinNodeTestCases
Mappings to QName marshalled qname currently looks like "ns0:something" as value in JSON
WriterRecordContentHandler inner class of WriterRecord Currently writes XML ouptut... when is this called?
JSONWriterRecord element(XPathFragment frag) { Currently writes XML ouptut... when is this called?
BinaryDataColletionMappings org.eclipse.persistence.testing.jaxb.externalizedmetadata.mappings.binarydatacollection.BinaryDataCollectionMappingTestCases

Back to the top