Looking back to Jackson 2.5(.0) features

Ok, back to the future, release by release, we’ll get to Jackson 2.5.0 from January 2015. As usual, 2.5 release notes spell out all the details, but here’s an abbreviated author’s cut of important inclusions.

General

No new components were introduced, nor were minimum requirements for platform (JDK, or Scala for Scala module) changed. But compared to 2.4 (I reviewed earlier), this version was a bit heavier with new and improved functionality, even without new components.

As usual, `jackson-databind` had most changes, but CSV module also had its share of important fixes. This was probably the first minor release with what I consider fully functional and usable CSV format support.

Notable new features

Here’s a sampling of things I think are most important. As usual, YMMV, so check out release notes for all the changes.

ObjectWriter.writeValues()

One common use case for JSON is for encoding longer data streams, where individual JSON Objects are written with linefeed as separator, something like:

{"name":"Bob","age":38}
{"name":"Alice","age":28}

Jackson already had convenience functionality for reading such sequences using `ObjectReader.readValues()`, like so:

ObjectMapper mapper = new ObjectMapper();
MappingIterator<Person> it = mapper.readerFor(Person.class)
.readValues(jsonSource);
while (reader.hasNextValue()) {
Person p = it.nextValue();
// ... process
}

but with 2.5, there’s also counterpart for producing such data files:

SequenceWriter w = mapper.writerFor(Person.class)
.writeValues(new File("persons.json"));
w.write(person1);
w.write(person2);
// ... and so forth
w.close();

It is worth noting that it was already possible to both read and write such sequence files, but it used to require explicit construction of underlying `JsonParser` / `JsonGenerator` via Streaming API.
So for the most part this is just for convenience; however, for reading side there is also additional limited error recovery which allows for continuing reading even if some of the entries have problems with databinding (but less likely if there are low-level decoding issues).

Besides JSON handling, these methods are particularly useful with CSV, finally making incremental (streaming) reading and writing of CSV as convenient (but more efficient) than reading/writing the whole CSV document as List or array of POJOs.

`@JsonAppend` (Virtual Properties)

One often-requested feature for Jackson had been ability to “decorate” values output: append or prepend JSON Object properties that do not exist in POJO to write. 2.5 adds new general-purpose “Virtual Property” concept that allows for including such properties, as well as simple annotation-based implementation that makes use of this facility.

A simple use case for annotation-based approach would be:

@JsonAppend(prepend=true, attrs={ @JsonAppend.Attr(“id”) })
static class SimpleBeanPrepend {
public int value = 13;
}
...
String json = WRITER.withAttribute("id", "abc123")
.writeValueAsString(new SimpleBean());
// -> {"value":13,"id":"abc123"}

Note that there are many aspects you can configure via `@JsonAppend` annotation, including whether append (default) or prepend virtual property and what is the name of attribute to use (defaults to name of property to output).

But perhaps more importantly there is also a variation of `@JsonAppend.Prop` (instead of `@JsonAppend.Attr` shown above) that allows fully custom implementations to be hooked in like so:

@JsonAppend(prepend=true, props={
@JsonAppend.Prop(value=CustomVProperty.class, name=”id”) })
})
class CustomVBean {
public int value = 72;
}

where `CustomVProperty` is a `VirtualBeanPropertyWriter` and can customize even wider range of things. This is another feature that I should probably show-case with better examples; but for now check out test `TestVirtualProperties` if you want to see more details.

`MapperFeature.ACCEPT_CASE_INSENSITIVE`

Another highly requested feature was the ability to map case-insensitive property names (in JSON) into rigidly named POJO properties. Now it is possible to enable this, globally, like so:

ObjectMapper mapper = ...
mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);

after which JSON property “Name” would match POJO property inferred by `getName()`, without other configuration or annotations.

NOTE: in general, it is still preferable to use appropriate `PropertyNamingStrategy` if naming scheme is regular, or if only small deviations exist (which can be handled by a few`@JsonProperty` annotations). Case-insensitive handling incurs some performance overhead.

`@JsonInclude(content=Include.NON_NULL)`

After addition of `@JsonInclude`, to suppress serialization of Java nulls (with `NON_NULL`), or empty Lists/Maps/arrays (`NON_EMPTY`), one problem soon became apparent: although filtering works for POJO property values, it only worked at Java level. That is, while empty Map value like:

“stuff”: { }

it wouldn’t be possible to filter out logically empty (from JSON perspective) values like:

“stuff” : {
"value" : null,
"extra" : null
}

because check for emptiness is done directly on Map; and further, even if checks could be applied, there was no way to annotate properties to specify appropriate criteria.

2.5 added both ability to separately annotate inclusion criteria for “value” and “content” it has (if any):

@JsonInclude(content=JsonInclude.Include.NON_EMPTY,
value=JsonInclude.Include.NON_EMPTY)
public Map<String,String> properties;

NOTE: some implementation issues limited actual usability as of 2.5, and there have been set of changes in Jackson 2.6 and 2.7 to make sure various combinations work — so make sure to use a later version with this feature.

“Current value” access for reading/writing

One more particular powerful new feature is the concept of “current value”: Java value being currently (that is, at the point where handler like custom serializer or deserializer) read or written. The importance is in the fact that although `JsonSerializer` and `JsonDeserializer` instances know specific current value being read or written, they do not know what is the “parent” Object (or which property is being written). But with combination of hierarchic reading/writing context (`JsonParser.getParsingContext()`, `JsonGenerator.getOuputContext()`) and method `getCurrentValue()` from context objects allows for actually determining position within Object hierarchy.

This feature is of particular importance for those cases where serializer or deserializers wants to impose specific handling for just certain properties of certain objects.

Method `getCurrentValue()` was also added in `JsonParser` and `JsonGenerator`; it simply delegates to the context object.

Re-Configurability of `JsonParser.Feature`, `JsonGenerator.Feature`

And to top of the overview, a smaller incremental improvement was the ability to dynamically change JsonParser/JsonGenerator Features, on per-call basis, from databind.

Before 2.5, it was necessary to directly configure `JsonParser` or `JsonGenerator` instance, if different settings were needed. But with 2.5 `ObjectReader` and `ObjectWriter` have methods to change settings on per-call basis:

ObjectReader r = mapper.readerFor(MyPojo.class)
.with(JsonParser.Feature.ALLOW_COMMENTS);
MyPojo value = r.readValue(new File("config.json"));

(and similarly for `ObjectWriter`)

Retrospect for 2.5

Jackson 2.5 ended up being quite a long-lived branch (about full year from .0 to 2.5.5, last patch), and as I write this, the most widely used `jackson-databind` version seems to actually still be 2.5.3.

Like what you read? Give @cowtowncoder a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.