Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Difference between revisions of "EclipseLink/DesignDocs/350483"
(→Backlog) |
(→Backlog) |
||
Line 678: | Line 678: | ||
|Number format | |Number format | ||
|Test JSON supported number formats. See [[http://www.json.org/ http://www.json.org/]] | |Test JSON supported number formats. See [[http://www.json.org/ http://www.json.org/]] | ||
+ | | | ||
+ | |- | ||
+ | |CollectionReference mappings | ||
+ | |Currently will marshal id:1,id:2 instead of id:[1,2] | ||
| | | | ||
|} | |} |
Revision as of 16:35, 24 August 2011
Contents
- 1 Design Specification: Object-to-JSON Binding Layer
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)
Concepts
See http://www.json.org/ for more JSON reference
Requirements
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> </foo>
And the JSON representation is:
{"foo" : { "id" : 123, "bar : "Hello World" }}
The goal is to have this supported by one object model:
@XmlRootElement(namespace="urn:example") public class Foo { @XmlAttribute private int id; @XmlElement(namespace="urn:example") 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; 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("bdoughan"); 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); } }
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:
javax.ws.rs.ext.MessageBodyReader
T readFrom(java.lang.Class<T> type, java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, MediaType mediaType, MultivaluedMap<java.lang.String,java.lang.String> httpHeaders, java.io.InputStream entityStream) throws java.io.IOException, WebApplicationException { Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); unmarshaller.setProperty("eclipselink.media.type", mediaType.toString(); return unmarshaller.unmarshal(entityStream); }
javax.ws.rs.ext.MessageBodyWriter
void writeTo(T t, java.lang.Class<?> type, java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, MediaType mediaType, MultivaluedMap<java.lang.String,java.lang.Object> httpHeaders, java.io.OutputStream entityStream) throws java.io.IOException, WebApplicationException { Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty("eclipselink.media.type", mediaType.toString(); marshaller.marshal(t, entityStream); }
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 | No |
XmlAccessorType | No |
XmlJavaTypeAdapter | No |
XmlJavaTypeAdapters | No |
XmlSeeAlso | No |
XmlTransient | No |
XmlType | No |
Applicable to JSON but Require Work
The following annotations will require changes to the OXM layer in order to support.
Annotation | Tested |
---|---|
XmlAnyAttribute | No |
XmlAnyElement | No |
XmlAttribute | Yes |
XmlElement | Yes |
XmlElementWrapper | No |
XmlID | No |
XmlIDREF | No |
XmlList | No |
XmlRootElement | Yes |
XmlValue | No |
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 | |
XmlInverseReference | |
XmlNameTransformer | |
XmlProperties | |
XmlProperty | |
XmlReadOnly | Yes |
XmlReadTransformer | |
XmlTransformation | Yes |
XmlVirtualAccessMethods | |
XmlVirtualAccessMethodsSchema | |
XmlWriteOnly | Yes |
XmlWriteTransformer | |
XmlWriteTransformers |
Applicable to JSON but Requires Work
The following annotations will require changes to the OXM layer in order to support.
Annotation | Tested |
---|---|
XmlClassExtractor | |
XMLDiscriminatorNode | |
XMLDiscriminatorValue | |
XmlElementsJoinNodes | |
XmlInlineBinaryData | |
XmlIsSetNullPolicy | |
XmlJoinNode | Yes |
XmlJoinNodes | |
XmlKey | |
XmlMashalNullRepresentation | |
XmlNullPolicy | |
XmlParameter | |
XmlPath | |
XmlPaths |
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 |
---|---|
XmlCDATA |
Design / Functionality
High Level Design
Unmarshalling
We will implement a JSONReader that will convert JSON input (InputStream, Reader, System ID) into SAX events.
Marshalling
We will implement two new records JSONWriterRecord and JSONFormattedWriterRecord that will handle the JSON output.
Low Level Design
No "Root Element" Support
JSON supports documents with no root element:
{"area-code":"613", "number":"1234567"}
During marshal if there is no @XmlRootElement specified then the JSON document won't have a root element (as above). During an unmarshal operation if the document has more than one child element it will be treated as an object without a root element. The only unmarshal methods that will be supported for the non root element case will be those that take a Class argument that specifies the class to unmarshal to:
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);
If there is an object with no root element that only has 1 mapped field it will marshal fine but will not unmarshal correctly. This is because the check to determine if this is a root vs. non-root case is to check if there is more than one child element at the root level. To override this behavior users will be able to specify a property on the JAXBUnmarshaller to specify that it is a non-root element case.
jaxbUnmarshaller.setProperty(JAXBContext.JSON_HAS_ROOT_ELEMENT, false);
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.
{"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.
jsonUnmarshaller.setProperty(JAXBContext.JSON_ATTRIBUTE_PREFIX, "@"); jsonMarshaller.setProperty(JAXBContext.JSON_ATTRIBUTE_PREFIX, "@") ;
{"phone":{ "area-code":"613", "@number":"1234567" } }
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.
Map namespaces = new HashMap<String, String>(); namespaces.put("ns1", "namespace1"); namespaces.put("ns2", "namespace2"); jsonUnmarshaller.setProperty(JAXBContext.JSON_NAMESPACES, namespaces);
If the namespace map is set on the marshaller it will be used to prefix elements during the marshal operation.
jsonMarshaller.setProperty(JAXBContext.JSON_USE_NAMESPACES, true);
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
XML - <prefix:vehicle xsi:type="prefix:car-type"> JSON-Can unmarshal "type":"prefix:car-type" or "type":"car-type", Should it marshal "type":"prefix:car-type" or "type":"car-type",
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);
{"emp":{ "address":null }
Complex Object example employee.setAddress(new Address());
{"emp":{ "address":{} }
Simple example address.setCity(null);
{"address":{ "city":null } }
XmlValue
2 options for XmlValue behavior (ideally we will support both). If Phone.java has 1 field called number and it is marked with @XmlValue Option1- use a pair name called "value" (or something customizable)
{"employee":{ {"phone":{ "value":"123-4567" } }}
Option2
{"employee":{ {"phone":"123-4567" }}
XSI type attribute
ie:CompositeObjectMapping to Object.class attributes of type java.lang.Object (or Collection of Objects).
- Equivalent XML -
<responsibilities> <responsibility xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xsi:type="xsd:string">Fix Bugs</responsibility>
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":
Unmarshaller jsonUnmarshaller = jaxbContext.createUnmarshaller(); jsonUnmarshaller.setProperty("eclipselink.media.type", "application/json");
Marshaller
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("eclipselink.media.type", "application/xml");
JAXBContext
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("eclipselink.media.type", "application/json"); JAXBContext jaxbContext = JAXBContext.newInstance(new Class[] {Root.class}, properties);
GUI
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.
Documentation
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 |
---|---|---|
Decisions
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. |
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
Item | Descripton | Effort |
---|---|---|
Mappings to Object | In XML we would write out xsi:type to preserve the actual type. | |
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. | |
Inheritance | For the type attribute should JSON marshal "type":"prefix:car-type" or "type":"car-type" | |
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, "@"); | |
AnyAttribute/AnyObject mappings | Duplication will likely occur since during unmarshal everything is reported as both an attribute and an element | |
XmlValue and XmlPath("text()") | No current support. See [XmlValue] | |
XmlPath('.') | ||
Inline binary mappings | Handled differently than direct mappings since direct mappings end in text() or are attributes but binary data mappings don't | |
Server Tests | No current JAXB Server Tests exist | |
Formatting | Confirm desired formatting and add some formatted tests. | |
Escape characters | Test JSON escape characters are handled properly. See [http://www.json.org/] | |
Number format | Test JSON supported number formats. See [http://www.json.org/] | |
CollectionReference mappings | Currently will marshal id:1,id:2 instead of id:[1,2] |