Monday, March 26, 2012

Jackson 2.0.0 released: going GitHub, handling cyclic graphs, builder style...

After furious weeks of coding and testing, the first major version of upgrade of Jackson is here: 2.0 was just released, and available (from Download page, for example)

1. Major Upgrade?

So how does this upgrade differ from "minor" upgrades (like, from 1.8 to 1.9)? Difference is not based on amount of new functionality introduced -- most Jackson 'minor' releases have contained as much new stuff as major releases of other projects -- although 2.0 does indeed pack up lots of goodies.

Rather, major version bump indicates that code that uses Jackson 1.x is neither backwards nor forwards compatible with Jackson 2.0.
That is, you can not just replace 1.9 jars with 2.0 and hope that things work. They will not.

Why not? 2.0 code differs from 1.x with respect to packaging, such that:

  1. Java package used is "com.fasterxml.jackson" (instead of "org.codehaus.jackson")
  2. Maven group ids begin with "com.fasterxml.jackson" (instead of "org.codehaus.jackson")
  3. Maven artifact ids have change a bit too (core has been split into "core" and "annotations", for example)

These are actually not big changes in and of itself: you just need to change Maven dependencies, and for Java package, change import statements. While some amount of work, these are mechanical changes. But it does mean that upgrade is not basic plug-n-play operation.

In addition, some classes have moved within package hierarchy, to better align functional areas. Some have been refactored or carved (most notably, SerializationConfig.Feature is now simply SerializationFeature, and DeserializationConfig.Feature is now DeserializationFeature). Most cases of types moving should be easy to solve with IDEs, but we will also try to collect some sort of upgrade guide.

For more details on packaging changes, check out "Jackson 2.0 release notes" page.

1.1 Why changes to package names?

The reason for choosing to move to new packages is to allow both Jackson 1.x and Jackson 2.x versions to be used concurrently. While smaller projects will find it easier to just convert wholesale, many bigger systems and (especially) frameworks will find ability to do incremental upgrades useful. Without repackaging one would have to upgrade in "all-or-nothing" way. But with repackaging this can be avoided, and existing functionality converted gradually (within some limits; transitive dependencies may still be problematic).

2. But wait! It is totally worth it!

I started with the "bad news" first, to get that out of the way, since there is lots to like about the new version.
I will write more detailed articles on specific features later on, but let's start with a brief overview.

2.1 Community improvements: Better collaboration with GitHub

First big change is that Jackson project as a whole has moved to Github. While many extension projects (modules) had already started there, now all core components have moved as well:

as well as standard extension components such as:

and many, many more (total project count is 17!)

This should help make it much easier to contribute to projects; as well as make it easier for packages to evolve at appropriate pace: there is less need to synchronize "big" releases outside of 3 core packages, and it is much easier to give scoped access to new contributors.

2.2 Feature: Handle Any Object Graphs, even Cyclic ones!

One of biggest so far unsupported use case been ability to handle serialization and deserialization of cyclic graphs, and elimination of duplicates due to shared references. Although existing @JsonManagedReference annotation works for some cases (esp. many ORM-induced parent/child cases), there has been no general solution.

But now there is. Jackson 2.0 adds support for concept called "Object Identity":ability to serialize Object Id for values, use this id for secondary references; and ability to resolve these references when deserializing). This feature has many similarities to "Polymorphic Type information" handling which was introduced in Jackson 1.5.

Although full explanation of how things work deserves its own article, the basic idea is simple: you will need to annotate classes with new annotation @JsonIdentityInfo (or, use it for properties that reference type for which to add support), similar to how @JsonTypeInfo is used for including type id:


  @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
  public class Identifiable {
    public int value;

    public Identifiable next;
  }

and with such definition, you could serialize following cyclic two-node graph:


Identifiable ob1 = new Identifiable(); ob1.value = 13; Identifiable ob2 = new Identifiable(); ob2.value = 42; // link as a cycle: ob1.next = ob2; ob2.next = ob1; // and serialize! String json = objectMapper.writeValueAsString(ob1);

to get JSON like:

  {
   "@id" : 1,
   "value" : 13,
   "next" : {
    "@id" : 2,
    "value" : 42,
    "next" : 1
   }
  }

and obvious deserialize it back with:

  Identifiable result = objectMapper.readValue(json, Identifiable.class);

assertSame(ob1.next.next, ob1);

Most details (such as id generation algorithm used, property use for inclusions etc) are configurable; more on this on a later article.
Until then, Javadocs should help.

2.3 Feature: Support "Builder" style of POJO construction

Another highly-requested feature has been ability to support POJOs created using "Builder" style. This means that POJOs are created using a separate Builder object which has methods for changing property values; and a "build" method that will create actual immutable POJO instance. For example, considering following hypothetical Builder class:


 public class ValueBuilder {
  private int x, y;

  // can use @JsonCreator to use non-default ctor, inject values etc
  public ValueBuilder() { }

  // if name is "withXxx", works as is: otherwise use @JsonProperty("x") or @JsonSetter("x")!
  public ValueBuilder withX(int x) {
    this.x = x;
    return this; // or, construct new instance, return that
  }
  public ValueBuilder withY(int y) {
    this.y = y;
    return this;
  }
  public Value build() {
    return new Value(x, y);
  }
}

and value class it creates:

@JsonDeserialize(builder=ValueBuilder.class) // important!
public class Value {
  private final int x, y;
  protected Value(int x, int y) {
    this.x = x;
    this.y = y;
  }
}

we would just use it as expected, as long annotations have been used as shown above:

  Value v = objectMapper.readValue(json, Value.class);

and it "just works"

2.4 Ergonomics: Simpler, more powerful configuration

Although ObjectMapper's immutable friends -- ObjectReader and ObjectWriter -- were introduced much earlier, 2.0 will give more firepower for both, making them in many ways superior to use of ObjectMapper. In fact, while you can still pass ObjectMappers and create ObjectReaders, ObjectWriters on the fly, it is recommend that you use latter if possible.

So what was the problem solved? Basically, ObjectMapper is thread-safe if (and only if!) it is fully configured before its first use. This means that you can not (or, at least, not supposed) to try to change its configuration once you have used it. To further complicate things, some configuration options would work even if used after first read or write, whereas others would not, or would only work in seemingly arbitrary cases (depending on what was cached).

On the other hand, ObjectReader and ObjectWriter are fully immutable and thus thread-safe, but would also allow creation of newly configured instances. But while this allowed handling of some cases -- such as that of using different JSON View for deserialization -- number of methods available for reconfiguration was limited.

Jackson 2.0 adds significant number of new fluent methods for ObjectReader and ObjectWriter to reconfigure things; and most notably, it is now possible to change serialization and deserialization features (SerializationFeature, DeserializationFeature, as noted earlier). So, to, say, serialize a value using "pretty printer" you could use:

  ObjectWriter writer = ObjectMapper.writer(); // there are also many other convenience versions...
  writer.withDefaultPrettyPrinter().writeValue(resultFile, value);

or to enable "root element" wrapping AND specifying alternative wrapper property name:

  String json = writer
    .with(SerializationFeature.WRAP_ROOT_VALUE)
    .withRootName("wrapper")
    .writeValueAsString(value);

basically, anything that can work on per-call basis will now work through either ObjectReader (for deserialization) or ObjectWriter (for serialization).

2.5 Feature parity: JSON Views for deserialization

One of frustrations with Jackson 1.x has been that all filtering functionality has been limited to serialization side. Not any more: it is now possible to use JSON Views for deserialization as well:

  Value v = mapper
   .reader(Value.class)
   .withView(MyView.class) 
   .readValue(json);

and if input happened to contain properties not included in the view, values would be ignored without setting matching POJO properties.

2.6 Custom annotations using Annotation Bundles

Another ergonomic feature is so-called "annotation bundles". Basically, by addition of meta-annotation @JacksonAnnotationsInside, it is now possible to specify that annotations from a given (custom) annotations should be introspected and used same way as if annotations were directly included. So, for example you could define following annotation:

  @Retention(RetentionPolicy.RUNTIME)
  @JacksonAnnotationsInside
  @JsonInclude(Include.NON_NULL) // only include non-null properties
  @JsonPropertyOrder({ "id", "name" }) // ensure that 'id' and 'name' are always serialized before other properties
  private @interface StdAnnotations

and use it for POJO types as a short-hand:

  @StdAnnotations
  public class Pojo { ... }

instead of separately adding multiple annotations.

2.7 @JsonUnwrapped.prefix / suffix

One more cool new addition is for @JsonUnwrapped annotation (introduced in 1.9). It is now possibly to define prefix and/or suffix to use for "unwrapped" properties, like so:

  public class Box {
    @JsonUnwrapped(prefix="topLeft") Point tl;
    @JsonUnwrapped(prefix="bottomRight") Point br;
  }
  public class Point {
    int x, y;
  }

which would result in JSON like:

  {
   "topLeft.x" : 0,
   "topLeft.y" : 0,
   "bottomRight.x" : 100,
   "bottomRight.y" : 80  
  }

This feature basically allows for scoping things to avoid naming collisions. It can also be used for fancier stuff, such as binding of 'flat' properties into hierarchic POJOs... but more on this in a follow-up article.

3.0 And that's most of it, Folks!

At least for now. Stay tuned!

EDIT:

Links to the continuing "Jackson 2.0 saga":

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.