Design Specification: Object-to-JSON Binding Layer

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.

Goals:

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)

Low Level Design

No "Root Element" Support

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("JAXBContext.JSON_INCLUDE_ROOT", false);

Unmarshal

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 th option to unmarshal documents that have roots and documents that don`t have roots. By default it will be assumed that the document does not have a root element. This can be changed by setting the following property on the unmarshaller.

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.

{"address":
"id":1,
"city":"Ottawa",
"isMailingAddress":true
}

Attributes

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

Namespaces

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.

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

{"ns0.employee:{
"ns0.id":123
}
}

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.

Inheritance

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"

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.

Testing

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.

API

MediaType - New Enum

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:

APPLICATION_XML

APPLICATION_JSON

New MOXy Specific JAXB Property ("eclipselink.media-type")

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

Unmarshaller

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 "application/xml":

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.

Backlog

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

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)

4. Mapping collection to attribute

CompositeDirectCollectionMapping to an attribute (ie: itmem/@type should work in JSON and look like

{"test":{

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

}}

5. CharacterEscapeHandler

Object with no XmlRootElement should marshal/unmarshal even if include_root not set

XmlMixed

Tests

More tests with no root element. More tests with namespaces.

Test with "value" and "type" variables that could conflict with xsi:type and value wrapper properties.

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

Server Tests

No current JAXB Server Tests exist

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.

Completed

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, "@");