MOXy’s Object Graphs – Input/Output Partial Models to XML & JSON
Suppose you have a domain model that you want to expose as a RESTful service. The problem is you only want to input/output part of your data. Previously you would have created a separate model representing the subset and then have code to move data between the models. In EclipseLink 2.5.0 we have a new feature called Object Graphs that enables you to easily define partial views on your model.
You can try this out today by downloading an EclipseLink 2.5.0 nightly download starting on March 24, 2013 from:
Java Model
Below is the Java model that we will use for this example. The model represents customer data. We will use an object graph to output just enough information so that someone could contact the customer by phone.
Customer
The @XmlNamedObjectGraph extension is used to specify subsets of the model we wish to marshal/unmarshal. This is done by specifying one or more @XmlNamedAttributeNode annotations. If you want an object graph applied to a property you can specify a subgraph for it. The subgraph can either be defined as a @XmlNamedSubgraph or as a @XmlNamedObjectGraph on the target class.
package blog.objectgraphs.metadata; import java.util.List; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*; @XmlNamedObjectGraph( name='contact info', attributeNodes={ @XmlNamedAttributeNode('name'), @XmlNamedAttributeNode(value='billingAddress', subgraph='location'), @XmlNamedAttributeNode(value='phoneNumbers', subgraph='simple') }, subgraphs={ @XmlNamedSubgraph( name='location', attributeNodes = { @XmlNamedAttributeNode('city'), @XmlNamedAttributeNode('province') } ) } ) @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlAttribute private int id; private String name; private Address billingAddress; private Address shippingAddress; @XmlElementWrapper @XmlElement(name='phoneNumber') private List<PhoneNumber> phoneNumbers; }
Address
Because we defined the object graph for the Address class as a subgraph on the Customer class there is nothing we need to do here.
package blog.objectgraphs.metadata; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class Address { private String street; private String city; private String province; private String postalCode; }
PhoneNumber
For the phoneNumbers property on the Customer class we specified that an object graph called simple should be used to scope the data. We will define this object graph on the PhoneNumber class. An advantage of this approach is that it makes the object graphs easier to be reused.
package blog.objectgraphs.metadata; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*; @XmlNamedObjectGraph( name='simple', attributeNodes={ @XmlNamedAttributeNode('value'), } ) @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber { @XmlAttribute private String type; @XmlValue private String value; }
Demo Code
Demo
In the demo code below we will read in an XML document to fully populate our Java model. After marshalling it out to prove that everything was fully mapped we will specify an object graph on the marshaler (line 22), and output a subset to both XML and JSON.
package blog.objectgraphs.metadata; import java.io.File; import javax.xml.bind.*; import org.eclipse.persistence.jaxb.MarshallerProperties; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); File xml = new File('src/blog/objectgraphs/metadata/input.xml'); Customer customer = (Customer) unmarshaller.unmarshal(xml); // Output XML Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); // Output XML - Based on Object Graph marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, 'contact info'); marshaller.marshal(customer, System.out); // Output JSON - Based on Object Graph marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, 'application/json'); marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false); marshaller.setProperty(MarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true); marshaller.marshal(customer, System.out); } }
input.xml/Output
We will use the following document to populate our domain model. We will also marshal it back out to demonstrate that all of the content is actually mapped.
<?xml version='1.0' encoding='UTF-8'?> <customer id='123'> <name>Jane Doe</name> <billingAddress> <street>1 A Street</street> <city>Any Town</city> <province>Ontario</province> <postalCode>A1B 2C3</postalCode> </billingAddress> <shippingAddress> <street>2 B Road</street> <city>Another Place</city> <province>Quebec</province> <postalCode>X7Y 8Z9</postalCode> </shippingAddress> <phoneNumbers> <phoneNumber type='work'>555-1111</phoneNumber> <phoneNumber type='home'>555-2222</phoneNumber> </phoneNumbers> </customer>
XML Output Based on Object Graph
The XML below was produced by the exact same model as the previous XML document. The difference is that we leveraged a named object graph to select a subset of the mapped content.
<?xml version='1.0' encoding='UTF-8'?> <customer> <name>Jane Doe</name> <billingAddress> <city>Any Town</city> <province>Ontario</province> </billingAddress> <phoneNumbers> <phoneNumber>555-1111</phoneNumber> <phoneNumber>555-2222</phoneNumber> </phoneNumbers> </customer>
JSON Output Based on Object Graph
Below is the same subset as the previous XML document represented as JSON. We have used the new
JSON_WRAPPER_AS_ARRAY_NAME property (see Binding to JSON & XML – Handling Collections) to improve the representation of collection values.
{ 'name' : 'Jane Doe', 'billingAddress' : { 'city' : 'Any Town', 'province' : 'Ontario' }, 'phoneNumbers' : [ '555-1111', '555-2222' ] }
External Metadata
MOXy also offers an external binding document which allows you to provide metadata for third party objects or apply alternate mappings for your model (see: Mapping Object to Multiple XML Schemas – Weather Example). Below is the mapping document for this example.
<?xml version='1.0'?> <xml-bindings xmlns='http://www.eclipse.org/eclipselink/xsds/persistence/oxm' package-name='blog.objectgraphs.metadata' xml-accessor-type='FIELD'> <java-types> <java-type name='Customer'> <xml-named-object-graphs> <xml-named-object-graph name='contact info'> <xml-named-attribute-node name='name'/> <xml-named-attribute-node name='billingAddress' subgraph='location'/> <xml-named-attribute-node name='phoneNumbers' subgraph='simple'/> <xml-named-subgraph name='location'> <xml-named-attribute-node name='city'/> <xml-named-attribute-node name='province'/> </xml-named-subgraph> </xml-named-object-graph> </xml-named-object-graphs> <xml-root-element/> <java-attributes> <xml-attribute java-attribute='id'/> <xml-element java-attribute='phoneNumbers' name='phoneNumber'> <xml-element-wrapper/> </xml-element> </java-attributes> </java-type> <java-type name='PhoneNumber'> <xml-named-object-graphs> <xml-named-object-graph name='simple'> <xml-named-attribute-node name='value'/> </xml-named-object-graph> </xml-named-object-graphs> <java-attributes> <xml-attribute java-attribute='type'/> <xml-value java-attribute='value'/> </java-attributes> </java-type> </java-types> </xml-bindings>
Reference: MOXy’s Object Graphs – Input/Output Partial Models to XML & JSON from our JCG partner Blaise Doughan at the Java XML & JSON Binding blog.