Feature: Polymorphic Type Handling, PTH, (formerly known as "Polymorphic Deserialization (and serialization))
0. Implementation Status
As of February 2010, this feature *is implemented*, and will be part of official Jackson 1.5 release!
(for more details, see Jira entry http://jira.codehaus.org/browse/JACKSON-91)
1. Definition
Polymorphism is an Object-Oriented Design concept that Java implements by class inheritance. Here it just means ability to construct instances of sub-classes of a given declared class, based on which sub-class was actually serialized. That is, even though during serialization the declared type is a super-type, it should be possible for the deserializer to properly resolve actual expected type of the value to assign.
Implementing support for polymorphic types is one of highest priority items for Jackson development. At the same time, implementation that covers require use cases (including cases where not all communicating systems run on Java platform) is not trivially simple to implement, so care has to be taken to create a simple, powerful and extensible design. To come up with such a design, let's first consider type design choices and alternatives that are available.
2. Design Choices
Instance Type Information (Type Metadata)
(aka "Type Id")
To be able to deserialize JSON object into types that instances were serialized from (and not just statically declared type, which is generally a supertype), some amount of per-instance type information is needed. There are multiple possible ways to do this. For example:
Directly include Java class name as instance information (possibly either as fully-qualified name, or just relative name to minimize size). This is approach taken by package such as XStream
Include a type identifier that can be used to determine actual class: this is often done by using an external type definition (Schema). This is approach taken by frameworks like JAXB.
- Use some other custom type inclusion methods: type information might not necessarily limited to String values.
To give an idea of possible concrete examples of such instance type information, here are some examples (but please note that other choices discussed below would change actual mechanism of including type information):
{ // Using fully-qualified path
"@class" : "com.fasterxml.beans.EmployeeImpl", ...
}
{ // Using indirect type name
"@type" : "Employee", ...
}
{ // Fancy custom type information (can bind JSON object to a type object)
"customType" : { "xmlType" : "http://foo.bar/schema.xsd", "preferredClass" : "com.foo.EmployeeClass" },
...
}Different mechanisms have different trade-offs; for example:
- Direct inclusion of Java class names adds direct coupling to implementation classes and may make integration with non-Java systems difficult. But it is often the simplest solution if these limitations are acceptable.
Using indirect resolution adds complexity, which may be unnecessary for many use cases. Additionally JSON does not yet have adequate type definition language: the most complete Schema language, JSON Schema is lacking in its OO type support (focuses more on JSON-centric validation aspects). However, level of indirection allows for flexibility and can work well with heterogenous systems (javascript client, Java service is a common case)
- Custom type requires some amount of custom handling, but allows for ultimate flexibility for use cases that need it.
Methods for embedding Instance Type Information
(aka "Include As")
After deciding what type information (usually a Type Id of some kind) to add along with instance data, it is necessary to define how this information is to be included in actual JSON data. Some obvious methods of inclusion are:
- Include type information directly as metadata properties, along with actual instance data. One challenge here is that whereas XML has mechanisms for dividing namespace (both via attribute/element choice and by using XML Namespaces), all JSON properties live in same namespace; that is, there is no natural division between data and metadta
Use a wrapping mechanism, similar to how JAXB uses @XmlElements annotation (which is one way to achieve PMD)
- This style limits type metadata into JSON Strings, or types that can be converted to/from Strings (enums, numbers, other simple scalar types)
- JSON offers two obvious choices: JSON Object and JSON Array wrapper (see below)
Given examples from previous section, here are possible ways to embed class name information:
// Type name as a property, same as above
{
"@type" : "Employee",
...
}
// Wrapping class name as pseudo-property (similar to JAXB)
{
"com.fasterxml.beans.EmployeeImpl" : {
... // actual instance data without any metadata properties
}
}
// Wrapping class name as first element of wrapper array:
[
"com.fasterxml.beans.EmployeeImpl",
{
... // actual instance data without any metadata properties
}
]
Customizing type handling
Here are some aspects that should be customizable:
- It should be possible to add handlers to allow both custom type id handling (not just use class names or type id), and to allow alternative type metadata inclusion methods.
- In addition to per-type (class) annotations, it may be necessary (or at least very useful) to allow defining "default type handling" for classes of types: for example, enable type information inclusion for abstract types and interfaces.
- For resolving type names, a handler is likewise needed; this should be configurable, with some sensible default handler.
- When including type information as JSON properties, name of property to use should be configurable. For convenience there should be default property name that is based on type information style (for example, "@class" could be the default when using Java class name, and "@type" when including type name)
3. Implementation Plan
Current thinking is to support multiple Type Metadata methods as well as multiple inclusion methods. And since these are somewhat orthogonal, allow different combinations of the two. And finally make aspects configurable with sensible defaults, to try to fulfill sometimes conflicting goals of simplicity (as close to "zero configuration" as possible) and configurability (ability to customize behavior to exact needs and preferences).
As with other configurability for Jackson, emphasis will initially be on allowing configuration using Java Annotations. Benefits include some level of type-safety, as well as flexibility when using Annotation Mix-ins.
3.1 Baseline: global defaults
In addition to per-type definitions by annotations, it is necessary to allow setting of global baseline. There are two reasons why this is necessary:
- Convenience: although it may be technically possible to annotate all possible types (or might not...), it may be unfeasible for practical purposes. As such it is good to be able to define a default type handling baseline, different from default of "no type information".
(TO BE COMPLETED)
3.2 Per-type annotations
All annotations that do NOT depend on mapper types, will live in the main org.codehaus.jackson.annotate package. Other annotations (with dependencies) will live in org.codehaus.jackson.map.annotate package.
The main annotation used for indicating type is @JsonTypeInfo. It has following properties:
- use() for indicating type if mechanism used:
value: Id enumeration, choice of (CLASS, MINIMAL_CLASS, NAME, CUSTOM)
- include() for indication type inclusion mechanism:
value: As enumeration, choice of (PROPERTY, WRAPPER_ARRAY, WRAPPER_OBJECT)
property() used with PROPERTY inclusion mechanism, to indicate alternate property name to use (type ids define default property to use)
(TO BE COMPLETED)
3.3 Global type defaulting
(TO BE COMPLETED)
