Jackson 2.14 sneak peek
It has been a while since Jackson 2.13 was released — about 9 months.
Since then there has been steady progress, with about 50 fixes, improvements and new features (see in-progress 2.14 release notes for details).
The plan is to get the first Release Candidate (2.14.0-rc1) out during August 2022. Let’s have a look at some of interesting improvements, features that will be included.
Another “minor minor” release
One thing to note is that as with 2.13 — and different from Jackson 2.12– version 2.14 can be considered another “smaller” minor release: most improvements are incremental and there is no wide expansion of functionality (no new format or datatype modules)
Most notable improvements can be found in following areas:
- Improvements to JSON parser (performance, support for more “non-standard” content reading, alternate non-blocking input source)
- Datatype-specific configurability for
ObjectMapper
(andObjectReader
/ObjectWriter
) - More ways to use
JsonNode
(Tree Model)
Compatibility: Java 8 now baseline for jackson-core, jackson-jr
2.14 should be highly compatible with 2.13 even beyond the usual minor-to-minor compatibility. The only notable compatibility change is that Java 8 will now be the minimum requirement for jackson-core (streaming parser/generator) and jackson-jr modules. Jackson-annotations will remain the one and only module that still only requires Java 6 (until Jackson 3).
This is not expected to have much impact for users but simplifies maintenance: users (if any) that require Java 6 or Java 7 compatibility will still be able to use 2.13.x patches.
Feature: Improved @JsonAnySetter handling
Up until 2.14, @JsonAnySetter
has been usable either for
- 2-argument setter method (which gets called once per otherwise unmapped property), OR
Map<String, T>
valued non-null Field (in which case all otherwise unmapped properties are bound to an existing Map
But there had been long-running desire to extend this functionality to allow use of:
- Non-initialized (
null
)Map
whereinMap
is dynamically created if (and only if) needed (databind#3559) ObjectNode
(JsonNode
that represents JSON Object value) value Field (databind#3394)
So now you can use constructs like:
public class MapAnyBean {
// no need to initialize if one of "well-known" Map types:
@JsonAnySetter
public Map<String, Object> values;
}public class JsonNodeBean {
// May initialize but no need to:
@JsonAnySetter
public ObjectNode valuesAsNodes;
}
neither of which would have worked with Jackson 2.13 or earlier.
Feature: ByteBuffer-backed non-blocking JSON parser
As is well-known (or is it? :-) ) in addition to its default blocking I/O based parser, Jackson ALSO provides an efficient, fully-featured, world-class non-blocking (aka “async”) JSON parser implementation (since Jackson 2.9, see “Non-Blocking Reading” section).
But so far it has required content to be fed as raw byte arrays (byte[]
).
A long-standing — and Highly Voted! — wish (core#478) is now fulfilled: Jackson 2.14 will provide an alternate ByteBuffer
backed implementation — it works the same way except for different NonBlockingInputFeeder
(ByteBufferFeeder
instead of ByteArrayFeeder
). Refactoring work also means that it should now be much easier to add support for other non-blocking input sources.
Feature: Support for more Non-Standard “JSON” constructs
Over time there have been many requests to support decoding of “almost JSON” (or “JSON-like”) content; especially for things like:
- Optional/extra separators (trailing comma, missing values)
- Flexible number representations
- Optional comments
Existing options are mostly configurable using JsonReadFeature
(see jackson-core wiki for links to specific Feature options). Jackson 2.14 will add 2 more settings that can be useful for things like (badly named) “JSON5” content:
JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS
(jackson-core#773) to allow floating-point numbers like124.
and-8.
JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS
(jackson-core#774) to allow numbers with leading plus sign, like+25
and+0.17
Note: as per long-standing policy, all deviations from the strict JSON specification require to be explicitly enabled — they are not allowed by default.
Feature: FASTER floating-point number reading and writing
One area where JSON content handling has significant performance disadvantage compared to binary formats is that of floating-point (non-integer) number reading and writing.
Jackson 2.14 will now incorporate alternative, high(er)-performance algorithm (“Schubfac”) for reading and/or writing FP numbers in JSON content.
Features in question are:
StreamReadFeature.USE_FAST_DOUBLE_PARSER
(jackson-core#577)StreamWriteFeature.USE_FAST_DOUBLE_WRITER
(jackson-core#749)
and are usually enabled by configuring JsonFactory
like so:
JsonFactory f = JsonFactory.builder()
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
.enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER)
.build();
One thing to note is that there is a possibility that the conversion between textual value and the internal Java floating-point value (for float
, double
; BigDecimal
is not affected) may differ from that of conversion implemented by JDK: this is why the new read/write implementation is not enabled by default and has to be explicitly enabled by users.
Note, too, that in near future support will be added for other textual format backends too (CSV, Properties, TOML, XML, YAML). It is possible that this will be even included in 2.14 release.
We also hope to get some performance measurement numbers on difference these settings make — stay tuned!
Feature: datatype-specific configurability (JsonNodeFeature, EnumFeature?)
One “Bigger” feature, explained in JSTEP-7 proposal, is the ability to extend ObjectMapper
configurability since existing (pre-2.14) choices are limited to:
- Generic databinding Features (
DeserializationFeature
,SerializationFeature
,MapperFeature
), affecting all content - Generic and Format-specific low-level stream read/write features (like
JsonReadFeature
,CsvWriteFeature
etc) - Fully format-specific extensions through either format-specific schemas (
CsvSchema
,JavaPropsSchema
) or settings configurable only through format-mapper builder (CsvMapper.builder().setCSVSpecificThings(123)
) - Some serializers/deserializers may require direct configuration for exact specifics of Java class in question
But over time it has become clear that there is need for configuring handling of “categories of datatypes” like Date/Time; or “all Enum types”; or “Tree Model” types (JsonNode
subtypes); something sort of close to DeserializationFeature
/ SerializationFeature
but not extending to all datatypes.
So Jackson 2.14 now includes a mechanism with which we can easily add:
- Different kinds of
DatatypeFeature
implementations (as special kinds of Enums) - New entries to said implementations
with very little work within jackson-databind
itself: meaning that new kinds of Features and new Feature entries can and will be added incrementally in newer version.
Jackson 2.14 starts with a small set of features, and the expectation is that many more options will be added with later minor versions.
Initially included we have just 2 new options:
JsonNodeFeature.READ_NULL_PROPERTIES
(databind#3421)— whethernull
values from incoming JSON will result inNullNode
entries being added inObjectNode
(enabled) or not (disabled)JsonNodeFeature.WRITE_NULL_PROPERTIES
(databind#3476)— whetherNullNode
valued entries ofObjectNode
will be written out as JSONnull
s (enabled) or skipped (disabled)
and configuration will use the standard idiom of:
// May configure ObjectMapper default settings:
ObjectMapper mapper = JsonMapper.builder()
.disable(JsonNodeFeature.READ_NULL_PROPERTIES)
.build();
// and/or change for ObjectReaders/-Writers
JsonNode rootNode = mapper.readerFor(JsonNode.class)
.enable(JsonNodeFeature.READ_NULL_PROPERTIES)
.readValue(inputJson);
String json = mapper.writer()
.disable(JsonNodeFeature.WRITE_NULL_PROPERTIES)
.writeValueAsString(rootNode);
Same will be true for other DatatypeFeature
s added — like EnumFeature
(which may even still be added in 2.14 if I have time to) or DateTimeFeature
(which is highly requested but may need to wait until 2.15).
The main benefit is really for Jackson development team as it will be trivially easy to quickly add new configuration options. There is also minimal overhead for storing these configuration settings (they are backed by bit fields, similar to all other XxxFeature
options)
Feature: JsonNode.withObject(JsonPointer) / .withArray(JsonPointer)
Use of JsonPointer
with JsonNode
is quite convenient for read access:
JsonNode doc = mapper.readTree(docSource);
// From {"users: [
// { "name" : "Bob", "age" : 42 }
// ])int age = doc.at("/users/0/age").asInt();
but so far it has not been possible to easily MODIFY Tree Model — based documents.
But Jackson 2.14 adds two new methods (see [databind#1980]) to change this:
JsonNode.withObject(JsonPointer)
— will traverse specified path and ensure that at its end there will be a JSON Object (ObjectNode
), which is returnedJsonNode.withArray(JsonPointer)
— will traverse specific path and ensure that at its end there will be a JSON Array (ArrayNode
), which is returned
(as well as 2 overloads for more configurability wrt. what overwrites of existing nodes are allowed)
These methods will allow usage where JsonPointer
can be used to indicate Path that either exists OR — if not — WILL exist (that is, be created). So we could construct document like above with:
ObjectNode root = mapper.createObjectNode();
root.withObject(JsonPointer.compile("/users/0")).put("age", 42);
as well as replace existing value(s).
Other Modules
As per 2.14 Release Notes there are definitely more noteworthy changes than I have time to write for so if interested, feel free to poke around.
But of special note is Jackson Scala module which has its own release notes:
That’s All, Folks!
And there we have it. I hope to get the first (and possibly only) Release Candidate out Very Soon Now — and the official 2.14.0 out by September, 2022.