Fusion Middleware Documentation
Advanced Search


Solutions Guide for Oracle TopLink
Close Window

Table of Contents

Show All | Collapse

16 Converting Objects to and from JSON Documents

This chapter describes how Oracle TopLink 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 contains the following sections:

Use Case

Users need to convert objects to and from JSON documents.

Solution

TopLink provides JSON support through the EclipseLink MOXy implementation.

Components

  • TopLink 12c Release 1 (12.1.2) or later.

    Note:

    TopLink's core functionality is provided by EclipseLink, the open source persistence framework from the Eclipse Foundation. EclipseLink implements Java Persistence API (JPA), Java Architecture for XML Binding (JAXB), and other standards-based persistence technologies, plus extensions to those standards. TopLink includes all of EclipseLink, plus additional functionality from Oracle.

  • JSON documents.

Sample

See the following EclipseLink samples for related information:

16.1 Introduction to the Solution

EclipseLink supports all MOXy object-to-XML options when reading and writing JSON, including:

  • EclipseLink'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

16.2 Implementing the Solution

This section contains the following tasks for converting objects to and from JSON documents.

16.2.1 Task 1: Marshalling and Unmarshalling JSON Documents

Use the eclipselink.media-type property on your JAXB Marshaller or Unmarshaller to produce and use JSON documents with your application, as shown in Example 16-1.

Example 16-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");

...

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

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

You can also configure your application to use JSON documents by using the MarshallerProperties, UnmarshallerProperties, and MediaType constants, as shown in Example 16-3.

Example 16-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);
...

16.2.2 Task 2: Specifying JSON Bindings

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

Example 16-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);
    }
 
}

You can also write MOXy External Bindings files as JSON documents. Example 16-5 shows how to use bindings.json to map Customer and PhoneNumber classes to JSON.

Example 16-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 16-6 shows how to use the JSON file (created in Example 16-5) when bootstrapping a JAXBContext.

Example 16-6 Using JSON to Bootstrap a JAXBContext

Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put("eclipselink.oxm.metadata-source", "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"));
...

16.2.3 Task 3: Specifying JSON Data Types

Although XML has a single data type, JSON differentiates between strings, numbers, and booleans. EclipseLink supports these data types automatically, as shown in Example 16-7

Example 16-7 Using JSON Data Types

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

16.2.4 Task 4: Supporting Attributes

JSON does not use attributes; anything mapped with a @XmlAttribute annotation will be marshalled as an element. By default, EclipseLink 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 16-8. EclipseLink 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 @.

Example 16-8 Using a Prefix

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

You can also set the JSON_ATTRIBUTE_PREFIX property in the Map used when creating the JAXBContext, as shown in Example 16-9. All marshallers and unmarshallers created from the context will use the specified prefix.

Example 16-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);

16.2.5 Task 5: Supporting no Root Element

EclipseLink 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 16-10.

Example 16-10 Marshalling no Root Element Documents

marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);

When unmarshalling a document with no root elements, you should specify the class to which to unmarshal, as shown in Example 16-11.

Example 16-11 Unmarshalling no Root Element Documents

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

Note:

If the document has no root element, you must specify the class to unmarshal to.

16.2.6 Task 5 Using Namespaces

Because JSON does not use namespaces, by default all namespaces and prefixes are ignored when marshalling and unmarshalling. 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 EclipseLink, 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. EclipseLink will recognize the prefix during an unmarshal operation and the resulting Java objects will be placed in the proper namespaces.

Example 16-12 shows how to use the NAMESPACE_PREFIX_MAPPER property.

Example 16-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);

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, ':');

16.2.7 Task 6: Using Collections

By default, when marshalling to JSON, EclipseLink marshals empty collections as [ ], as shown in Example 16-13.

Example 16-13 Marshalling Empty Collections

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

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" : {
   }
}
 

16.2.8 Task 7: Mapping Root-Level Collections

If you use the @XmlRootElement(name="root") annotation to specify a root level, the JSON document can be marshalled 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);

16.2.9 Task 8: Wrapping Text Values

JAXB supports one or more @XmlAttributes on @XmlValue classes, as shown in Example 16-14.

Example 16-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;
   }
 
}

To produce a valid JSON document, EclipseLink uses a value wrapper, as shown in Example 16-15.

Example 16-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"
      } ]
   }
}

By default, EclipseLink 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 16-16.

Example 16-16 Customizing the Name of the Value Wrapper

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"
      } ]
   }
}

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

Example 16-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();

When specified in a Map, the Marshallers and Unmarshallers created from the JAXBContent will automatically use the specified value wrapper.

16.3 Additional Resources

See the following resources for more information about the technologies and tools used to implement the solutions in this chapter:

  • Developing JAXB Applications Using Oracle TopLink