Wednesday, March 17, 2010

Jackson 1.5: Polymorphic Type Handling, first steps

Now that Jackson 1.5 is out (ready for download?), it's good to start reviewing some candies from the goody bag. While 1.5 is, technically speaking, a "minor" release it is nothing but grand judging by number of added features .
In a way Jackson mirrors development of JDK: Jackson version 1.2 and 1.5 have contained tons of new stuff, whereas 1.3 and 1.4 have been more about incremental steady improvements and bug fixes.

Let's start with the top requested new feature: Polymorphic Type Handling.

1. Polymorphic Types: not just for serialization any more!

First things first: polymorphic types just refer to cases where there is a single declared property type (say, Animal), but multiple concrete subtypes (like Dog and Cat). Basic OO stuff, implemented in Java by sub-classes and interface implementation. Nothing unusual there.
Challenge is actually that of mapping this cleanly to data formats: as with relational model (SQL) and hierarchic model (XML), JSON has no native concept of inheritance (XML has neither, in itself; XML Schema does add support however).

Jackson has actually supported serialization of all types, including polymorphic subtypes, since first data binding enabled versions (0.9.5). This because Jackson by default uses actual runtime type when determining serializer to use (although it can be configured to only static, declared type, too) -- which is different from what JAXB does -- and thus has no trouble writing out all properties sub-type has.

But the problem is, without additional type information, it is not possible to determine intended subtype when deserializing, when there are multiple subtypes to choose from: when property has declared type of 'Animal', there is no way to know whether it should be reconstructed as "Dog" or "Cat". This is where PTH implementation comes in.

Obvious answer is that additional type information must be added in serialized JSON data to allow this later reconstruction. But there are multiple considerations as to how this should be done, including:

  • What kind of type information should be included (Java class name? Logical type name? something else?)
  • How (where) should this type information be included (as a property? using wrapper constructs?)
  • Do we always have to include this type information? (doing so adds unnecessary bloat in cases where type information is redundant)

Jackson 1.5 tackles these (and some other minor concerns) in an efficient, configurable, and I hope, elegant way.

2. Enough theory: show me the code!

Ok: chances are that you are not all THAT interested in details of how implementation was tricky, or what philosophical considerations went on in determining what to implement and how. So let's start with some toy code.

Considering case of a polymorphic class; say... Person base class, and Employee and Customer sub-classes (I have written an "Animal example" at FX wiki already, let's get some variety), and using a wrapper class such as:


public class Contact {
 public Person initiator; // who initiated contact
 public Person receiver; // with whome?
}

and Person (etc) classes as follows


public abstract class Person {
 String name;
}

public class Employee extends Person {
 long employeeId;
}

public class Customer extends Person {
 String ssn;
 ServiceLevel serviceLevel;
}

What do we need to do to make Contact properly deserializable? By default, no additional type information would be added, and deserialization would fail

There are two main ways to do this, covering slightly different use cases, and offering different levels of configurability.

3. Type Info, method #1: explicit annotations

First method is to add explicit @JsonTypeInfo annotation in the declared type (or its supertypes):

@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
public abstract class Person {
  //...
}  

which would basically enable addition of type information for any instances of Person, as well as its subtypes. With above configuration -- include fully-qualified Java class name as type identifier; include it as main-level additional (meta-)property using property name "@class" -- output might look something like:

{
  "@class" : "com.fasterxml.sample.Employee",
  "name" : "Billy Bacon",
  "employeeId" : 126509
}

(with additional type information bolded)

So far so good: it is bit extra work to sprinkle annotations (which can be added via mix-ins, of course), but nothing too bad. As long as you know types in advance. And you can configure handling pretty well (check out Javadocs for more details for @JsonTypeInfo, related; or if I get that far, later entries on advanced topics)

4. Type info, method #2: default typing

But there is another way, which requires much less code, although offering bit less configurability. Specifically, you can enable so-called "default typing" for ObjectMapper by:

  mapper.enableDefaultTyping(); // defaults for defaults (see below); include as wrapper-array, non-concrete types
  mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT); // all non-final types

which would then apply default type information (type id is always fully-qualified Java class; inclusion method is customizable but defaults to "wrapper array") to all types (classes) that fulfill applicability criteria: by default it would be Object.class and non-concrete types (abstract classes, interfaces). So in our case this would work, given that Person is an abstract class: and if it wasn't, we could use "ObjectMapper.DefaultTyping.NON_FINAL" to include type information on all non-final classes.

5. Is that All?

No, not at all. This is just the simplest way to start safely handling polymorphic types like, say, List<Object>.

What else there is to find is good subject for follow-up entries, but here are some teasers:

  • Java class name is not a good choice when you have non-Java clients or services to talk to -- if so, you can use "logical type name" (see @JsonTypeName and @JsonSubTypes for more info); much like what JAXB does. Or, even further, you can have fully customized type id converters! (as long as they can convert to/from JSON Strings)
  • If you don't like using main-level property names (maybe there's a conflict), you can use other styles; "wrapper Object", "wrapper Array"
  • If you like default typing, but don't like its selection criteria, you can create your own: just sub-class default TypeResolverBuilder, assign it to your ObjectMapper.
  • Similarly, you can define fully custom type information handler, using @JsonTypeResolver annotation -- this will be nicely integrated with the whole system, you only have to provide actual inclusion, but it will be called for any existing types.

But enough for now... more on 1.5 later.

blog comments powered by Disqus

Sponsored By


Related Blogs

(by Author (topics))

Powered By

About me

  • I am known as Cowtowncoder
  • Contact me at@yahoo.com
Check my profile to learn more.