10 Using JSON Documents
Starting in Release 2.4, Oracle TopLink MOXy supports the ability to convert objects to and from JSON (JavaScript Object Notation). This feature is useful when creating RESTful services; JAX-RS services can accept both XML and JSON messages.
This chapter includes the following sections:
Understanding JSON Documents
TopLink supports all MOXy object-to-XML options when reading and writing JSON, including:
-
TopLink's advanced and extended mapping features (in addition to the JAXB specification)
-
Storing mappings in external bindings files
-
Creating dynamic models with Dynamic JAXB
-
Building extensible models that support multitenant applications
Marshalling and Unmarshalling JSON Documents
Use the eclipselink.media-type property on your JAXB Marshaller or Unmarsaller to produce and use JSON documents with your application, as shown in Example 10-1.
You can also specify the eclipselink.media-type
property in the Map
of the properties used when you create the JAXBContext
, as shown in Example 10-2.
You can also configure your application to use JSON documents by using the MarshallerProperties
, UnmarshallerProperties
, and MediaType
constants, as shown in Example 10-3.
Example 10-1 Marshalling and Unmarshalling
... Marshaller m = jaxbContext.createMarshaller(); m.setProperty("eclipselink.media-type", "application/json"); Unmarshaller u = jaxbContext.createUnmarshaller(); u.setProperty("eclipselink.media-type", "application/json"); ...
Example 10-2 Using a Map
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.oxm.MediaType;
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("eclipselink.media-type", "application/json");
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Employee.class }, properties);
Marshaller jsonMarshaller = ctx.createMarshaller();
Unmarshaller jsonUnmarshaller = ctx.createUnmarshaller();
When specified in a Map, the Marshallers and Unmarshallers created from the JAXBContent
will automatically use the specified media type.
Example 10-3 Using MarshallerProperties and UnarshallerProperties
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(UnmarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON); ...
Specifying JSON Bindings
Example 10-4 shows a basic JSON binding that does not require compile time dependencies in addition to those required for normal JAXB usage. This example shows how to unmarshal JSON from a StreamSource
into the user object SearchResults
, add a new Result
to the collection, and then marshal the new collection to System.out
.
You can also write MOXy External Bindings files as JSON documents. Example 10-5 shows how to use bindings.json
to map Customer and PhoneNumber classes to JSON.
Example 10-6 shows how to use the JSON file (created in Example 10-5) when bootstrapping a JAXBContext
.
Example 10-4 Using Basic JSON Binding
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); } }
Example 10-5 Using External Bindings
{ "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"} ] } } ] } }
Example 10-6 Using JSON to Bootstrap 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")); ...
Specifying JSON Data Types
Although XML has a single datatype, JSON differentiates between strings, numbers, and booleans. TopLink supports these datatypes automatically, as shown in Example 10-7
Example 10-7 Using JSON Data Types
public class Address { private int id; private String city; private boolean isMailingAddress; }
{ "id" : 1, "city" : "Ottawa", "isMailingAddress" : true }
Supporting Attributes
JSON does not use attributes; anything mapped with a @XmlAttribute
annotation will be marshalled as an element. By default, TopLink triggers both the attribute and element events, thereby allowing either the mapped attribute or element to handle the value.
You can override this behavior by using the JSON_ATTRIBUTE_PREFIX
property to specify an attribute prefix, as shown in Example 10-8. TopLink prepends the prefix to the attribute name during marshal and will recognize it during unmarshal.
In the example below the number
field is mapped as an attribute with the prefix @.
You can also set the JSON_ATTRIBUTE_PREFIX
property in the Map used when creating the JAXBContext
, as shown in Example 10-9. All marshallers and unmarshalers created from the context will use the specified prefix.
Example 10-8 Using a Prefix
jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, "@"); jsonMarshaller.setProperty(MarshallerProperties.JSON_ATTRIBUTE_PREFIX, "@") ;
{
"phone" : {
"area-code" : "613",
"@number" : "1234567"
}
}
Example 10-9 Setting a Prefix in a Map
Map<String, Object> properties = new HashMap<String, Object>(); properties.put(JAXBContextProperties.JSON_ATTRIBUTE_PREFIX, "@"); JAXBContext ctx = JAXBContext.newInstance(new Class[] { Phone.class }, properties);
Supporting no Root Element
TopLink supports JSON documents without a root element. By default, if no @XmlRootElement
annotation exists, the marshalled JSON document will not have a root element. You can override this behavior (that is omit the root element from the JSON output, even if the @XmlRootElement
is specified) by setting the JSON_INCLUDE_ROOT
property when marshalling a document, as shown in Example 10-10.
When unmarshaling a document with no root elements, you should set the JSON_INCLUDE_ROOT
property as shown in Example 10-10.
Note:
If the document has no root element, you must specify the class to unmarshal to.
Example 10-10 Marshalling no Root Element Documents
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
Example 10-11 Unmarshalling no Root Element Documents
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false); JAXBElement<SearchResults> jaxbElement = unmarshaller.unmarshal(source, SearchResults.class);
Using Namespaces
Because JSON does not use namespces, by default all namespaces and prefixes are ignored when marshaling and unmarshaling. In some cases, this may be an issue if you have multiple mappings with the same local name – there will be no way to distinguish between the mappings.
With TopLink, you can supply a Map of namespace-to-prefix (or an instance of NamespacePrefixMapper
) to the Marshaller and Unmarshaller. The namespace prefix will appear in the marshalled document prepended to the element name. TopLink will recognize the prefix during an unmarshal operation and the resulting Java objects will be placed in the proper namespaces.
Example 10-12 shows how to use the NAMESPACE_PREFIX_MAPPER
property.
The MarshallerProperties.NAMESPACE_PREFIX_MAPPER
applies to both XML and JSON; UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER
is a JSON-only property. XML unmarshalling can obtain the namespace information directly from the document.
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 any custom character by using the JSON_NAMESPACE_SEPARATOR
property. Here, a colon ( : ) will be used instead:
jsonMarshaller.setProperty(MarshallerProperties.JSON_NAMESPACE_SEPARATOR, ':'); jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_SEPARATOR, ':');
Example 10-12 Using Namesapces
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);
Using Collections
By default, when marshalling to JSON, TopLink marshals empty collections as [ ]
, as shown in Example 10-13.
Use the JSON_MARSHAL_EMPTY_COLLECTIONS
property to override this behavior (so that empty collections are not marshalled at all).
jsonMarshaller.setProperty(MarshallerProperties.JSON_MARSHAL_EMPTY_COLLECTIONS, Boolean.FALSE) ;
{ "phone" : { } }
Example 10-13
{ "phone" : { "myList" : [ ] } }
Mapping Root-Level Collections
If you use the @XmlRootElement(name="root")
annotation to specify a root level, the JSON document can be marshaled as:
marshaller.marshal(myListOfRoots, System.out);
[ { "root" : { "name" : "aaa" } }, { "root" : { "name" : "bbb" } } ]
Because the root element is present in the document, you can unmarsal it using:
unmarshaller.unmarshal(json);
If the class does not have an @XmlRootElement
(or if JSON_INCLUDE_ROOT
= false), the marshal would produce:
[ { "name":"aaa" }, { "name":"bbb" } ]
Because the root element is not present, you must indicate the class to unmarshal to:
unmarshaller.unmarshal(json, Root.class);
Wrapping XML Values
JAXB supports one or more @XmlAttributes
on @XmlValue classes
, as shown in Example 10-14
To produce a valid JSON document, TopLink uses a value
wrapper, as shown in Example 10-15.
By default, TopLink uses value as the name of the wrapper. Use the JSON_VALUE_WRAPPER
property to customize the name of the value wrapper, as shown in Example 10-16.
You can also specify the JSON_VALUE_WRAPPER
property in the Map
of the properties used when you create the JAXBContext
, as shown in Example 10-17.
When specified in a Map, the Marshallers and Unmarshallers created from the JAXBContent
will automatically use the specified value wrapper.
Example 10-14 Using @XmlAttributes
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; } }
Example 10-15 Using a value Wrapper
{ "employee" : { "name" : "Bob Smith", "mainPhone" : { "areaCode" : "613", "value" : "555-5555" }, "otherPhones" : [ { "areaCode" : "613", "value" : "123-1234" }, { "areaCode" : "613", "value" : "345-3456" } ] } }
Example 10-16
jsonMarshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, "$"); jsonUnmarshaller.setProperty(UnmarshallerProperties.JSON_VALUE_WRAPPER, "$");
Would produce:
{ "employee" : { "name" : "Bob Smith", "mainPhone" : { "areaCode" : "613", "$" : "555-5555" }, "otherPhones" : [ { "areaCode" : "613", "$" : "123-1234" }, { "areaCode" : "613", "$" : "345-3456" } ] } }
Example 10-17 Using a Map
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();