Sunday, November 17, 2013

Jackson 2.3.0 released -- quick feature overview

Now that Jackson 2.3.0 is finally finalized and released (official release date 14th November, 2013), it is time for a quick sampling of new features. Note that this is a very limited sampling -- across all core components and modules, there are close to 100 closed features; some fixes, but most improvements of some kind.

So here's my list of 6 notable features.

1. JsonPointer support for Tree Model

One of most often asked features for Jackson has been ability to support a path language to traverse JSON. So with 2.3 we chose the simplest standardized alternative, JSON Pointer (version 3), and made Tree Model (JsonNode) allow navigating using it.

Usage is simple: for JSON like

"address" : { "street" : "2940 5th Ave", "zip" : 980021 }, "dimensions" : [ 10.0, 20.0, 15.0 ]

you could use expressions like:

JsonNode root = mapper.readTree(src);
int zip =root. at("/address/zipcode").asIntValue();
double height = root.add("/dimensions/1").asDoubleValue(); // assuming it's the second number in there

Also note that you can pre-compile JSON Pointer expressions with "JsonPointer.compile(...)", instead of passing Strings; however, pointer expressions are not particularly expensive to tokenize. JsonPointer instances also have full serialization and deserialization support, so you can conveniently use them as part of configuration data for things like, say, DropWizard Configuration objects.

2. @JsonFilter improvements

With earlier versions, it was only possible to define ids of filters to apply using @JsonFilter on classes. With 2.3.0 you can apply this annotation (as well as @JsonView) on properties as well, to use different filters for different instances of same class:

public class POJO {
@JsonFilter("filterA") public Value value1;
@JsonFilter("filterB")public Value value2;

// similarly with JsonView (was added in 2.2)
@JsonView(AlternateView.class) public AnotherValue property;

But this is not all! Applicability of JSON Filters is also expanded, so that in addition to regular POJOs and their properties, it will now apply to:

  1. Any getters (they will get filtered just like regular properties)
  2. Java Maps

and in future we may also add ability to filter out List or array elements; this will be possible since the filtering interfaces were extended allow alternate calls that specify value index, instead of property name.

3. Uniqueness checks

Although JSON specification does not mandate enforcing uniqueness of Object property names (and use of databinder on serialization should prevent generation of duplicates), there are situations where one would want to be extra careful, and make parser check uniqueness.

With 2.3.0, there are two new features you can turn on to cause an exception to be thrown if duplicate property names are encountered:

  1. DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY: when reading JSON Trees (JsonNode), and encountering a duplicate name, throw a JsonMappingException
  2. JsonParser.Feature.STRICT_DUPLICATE_DETECTION and JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION: when reading or writing JSON using Streaming API (either directly, or for data-binding or building/serializing Tree instances), duplicates will be reported by a JsonParsingException

The main difference (beyond applicability; first feature only affects cases of building a JsonNode out of JSON input) is that duplicate detection at Streaming API level does incur some overhead (up to 30-40% more time spent); whereas duplicate detection at Tree Model level has little if any overhead. Difference is due to additional storage and checking requirements, as Streaming API does not need to keep track of set of property names encountered unless checking is required, whereas Tree Model will have to keep track of properties anyway. As a consequence, tree level checks are basically close to free to add.

4, Object Id handling

There are two improvements to Object Identity handling.

First, by enabling, SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID you can loosen checks so that all values that are deemed equal (by calling Object.equals()) are considered "same object"; basically this allows canonicalization of objects. It mostly makes sense when using ORM libraries, or other data sources that may not use exact object instances; or if you want to reconstruct shared references from sources that do not support it.

Second, when using YAML data format module, all Object Id references are now written using YAML native constructs called anchors, and references handled as native references. Same is also true for Type Ids; YAML module will now use tags for this purpose.
This change should not onlt make result more compact and "YAML-looking", but should also improve interoperability with native YAML tools. Latter should be most useful for a common use case for YAML, that of configuration files, where you can more easily share common configuration blocks with anchors and references.

5. Contextual Attributes for custom (de)serialization

One thing that has been missing so far from both SerializaterProvider and DeserializationContext objects (both of which extends DatabindContext base class) has been ability to assign and access temporary values during serialization/deserialization. In absence of something like this, custom JsonSerializer and JsonDeserializer implementations have had to use ThreadLocal to retain such values, adding more complexity.

But not any more: starting with 2.3.0, there is concept of "databind attributes" (similar to, say, Servlet attributes), managed and accessed using two simple methods -- getAttribute(Object key) and setAttribute(Object key, Object value). These can be used for keeping track of per-call (serialization or deserialization) state, pass data between (de)serializers and so on. All values are cleared when context is created (for a readValue() or writeValue() call); and similarly values are cleared at the end, so that no explicit clean up is required.

This may not sound like a huge feature in itself, but it actually opens up interesting possibilities for future: specifically, it may make sense to add new "standard attributes" that are set by databind module itself, when specific feature is enabled. For example, perhaps it would make sense to keep track of POJO that is being currently serialized/deserialized, to be accessible to actual (de)serializers.

6. Null handling for serialization

Another relative small, but often requested feature is ability to control how Java nulls are serialized, using more granular control than global rules like "all nulls to be serialized as empty Strings" (which is possible to do already). This is supported by adding a new property for @JsonSerialize annotation:

public class POJO {
public Value value;

in which case instance of MyNullSerializer would be used to write a JSON value for property "value", if POJO property has value null.

7. Other misc improvements

JAX-RS module has a new mechanisms for fully customizing ObjectReader and ObjectWriter instances, above and beyond what module itself can do. You can find more details on Issue#33.

XML module has been improved as well; it is finally possible to properly serialize root-level array and java.util.Collection values. ObjectMapper.convertValue() now works properly.

CSV module now supports filtering using JSON Views and JSON Filters; this was not working correctly with earlier versions.

Related Blogs

(by Author (topics))

Powered By

About me

  • I am known as Cowtowncoder
  • Contact me
Check my profile to learn more.