Jackson 2.12 features
(5 Most Wanted features, XML module, Blackbird)
Jackson 2.12.0 was just released on November 29, 2020. It is another “minor” version — meaning that it should be fully backwards-compatible with previous 2.x versions — but packs in much more new functionality than 2.11 event though its development process took exactly as long: 7 months. So it is yet another big leap forward for Jackson 2.x series.
For full details of everything that is included in 2.12.0, please refer to 2.12 Release Notes page: this blog post is meant to give just a brief overview of some of most notably inclusions — after all, there are no fewer than 100 issues resolved (again, twice as many as 2.11!).
I also plan to write follow-up posts on most of the major features included in this post, to dig bit deeper. But first things first…
Acknowledgments!
One interesting (and pleasant) change during 2.12 development process was the significantly increased rate of feature contributions by the Jackson community. Part of this coincided with Hacktoberfest 2020 — Jackson project had a quite successful participation this year — and I also blogged a bit more about Jackson project, but regardless of reasons this was the first time that the majority of major new features were NOT implement by yours truly.
To celebrate this milestone in external contributions, I decided to write a separate 2.12 Acknowledgments page to try to draw some more attention to great contributions and contributors (but please note that the full CREDITS file is the ultimate source on everything that is contributed).
Compatibility
There are not many noteworthy changes to compatibility — full notes, once again, on 2.12 Release notes page — but there are some incremental changes:
- No more Scala 2.10 support (versions 2.11, 2.12 and 2.13 supported), due to difficulties in supporting it and low residual usage of Scala 2.10 variant
- Most components retain the JDK 7 baseline like before (and couple just JDK 6 baseline) but JDK 8 is now needed by: jackson-datatype-eclipse-collections (was already the case but not documented), jackson-dataformat-ion (due to the version of underlying codec needing Java 8) and jackson-datatype-guava (due to new Guava 21 baseline).
- Note that while both Guava and Joda modules indicate that their preferred dependency versions increased (Guava 21, Joda 2.10), modules themselves actually work with a wider set of versions (see Guava Module README for details on Guava compatibility)
New Module: Blackbird
One commonly used performance enhancement drug for Jackson — Jackson Afterburner module — has been showing some signs of old age, regarding its compatibility with newer JVMs (JVM 11+) and especially wrt. access restrictions.
But thanks to the amazing contribution by Steven S (see Acknowledgments) there is now a fully-functional drop-in replacement: Blackbird module! Besides having a cool and thematically compatible name (SR-71 Blackbird is a supersonic jet; afterburner is a common technique for supersonic planes), Blackbird is designed to work better with newer JVMs using new constructs available (such as LambdaMetafactory).
Use of this module is very similar to that of Afterburner: that is, register the module and use ObjectMapper:
ObjectMapper mapper = JsonMapper.builder()
.addModule(new BlackbirdModule())
.build();
MyValue value = mapper.readerFor(MyValue.class)
.readValue(new File("stuff.json"));
While Blackbird does also work on Java 8, at this point it is mostly recommended for developers running on later JVMs.
Module Improvements: XML
One of long-term challenges has been properly supporting XML format: although jackson-dataformat-xml
is one of the oldest format modules, XML as a format is also possibly the most difficult one to support (of ones supported) — its logical content model is surprisingly different from that of JSON and requires special handling.
As a result there have been difficulties in handling things like:
- Repeated elements: before 2.12, this was only possible with Collection/array types (and even with them, nesting has often been problematic)
- Mixed content (XML elements containing both other elements AND textual content)
- Root value handling not as good as property values
- Support for
xsi:nil
incomplete - Empty element handling either not working, or coerces to
null
even for POJOs - JAXB-style “unwrapped” Lists not working reliably (especially nested ones)
Jackson 2.12 improves handling of all these cases: for some, more future work may be needed still, but overall functionality is improved so much that if you have had issues with XML before, please make sure to try out 2.12.0.
I will write more about specifics on a different blog post.
Module Improvements: Other
Aside from XML module, 12 other modules had issues resolved. For example:
- YAML module now supports Binary, Octal and Hexadecimal numbers(see #233); allows customization of escaping (quoting of textual values) details via pluggable
StringQuotingChecker
implementations (see #229); and allows deserializing empty String either asnull
or Empty String (see #130). A few issues (see #231, #232) were also resolved regarding Object and Type Ids (anchors, tags in YAML parlance) - Java 8 Date/Time module: allows use of
@JsonFormat
forDuration
type (see #184); appliesObjectMapper.setTimeZone()
overrides as necessary (see #175) - Kotlin and Scala module both fixed multiple issues — Scala module, in particular, is catching up with features of core components (being older it has lagged somewhat with support for things like Deep Merge and JsonFormat overrides)
- CBOR, CSV and Ion format modules all received multiple fixes as well
Most Wanted Features (5!)
One concept added recently is the tagging of “Most Wanted” features: this is done by adding “most-wanted” label (currently mostly on jackson-databind repo) to denote issues that seem to either get Github thumbs-up reactions or have been asked about often enough on other channels (mailing list, Gitter databind chat). While the mechanism is relatively new for Jackson project, the general idea of “most wanted” feature has been around for a while: a typical minor release has usually container one or two such features.
Jackson 2.12 contains no fewer than FIVE of Most Wanted features (“Jackson 5”? :) )
This is pretty cool on its own, but what is even more remarkable is that 4 out of 5 features were not designed and implement by me.
With that, these features are — in order of seniority (first requested first) — as follows:
- databind#43:
@JsonTypeInfo(use=DEDUCTION)
(requested 8 years ago, in 2012!) — polymorphic deserialization without adding explicit Type Id - databind#1296:
@JsonIncludeProperties({ "id", "name" })
(requested 4 years ago) — use “opt-in” style to ensure only properties you want are serialized - databind#1498: Annotationless
@JsonCreator
even for 1-parameter constructors — annotation-free properties-based creators worked before, but not for potentially ambiguous case of single argument. NOTE: implemented viaConstructorDetector
abstraction, a set of default ones - databind#2113:
CoercionConfig
to allow (or not) type coercions — allow (or not) coercion from, say, integer to Boolean; or from “145” to integer - databind#2709:
java.lang.Record
support (JDK 14+) — Records handled similar to POJOs without any extra annotations (but may use annotations to rename, override handling etc)
I will blog a bit more about all of these, but here are some random notes:
java.lang.Record
support is included in jackson-databind (and not as separate JDK-specific module), implemented without requiring JDK beyond JDK 7 — but obviously you need JDK 14 or later for Record types themselves. This feature was a good example of the awesome collaboration not just within Jackson community, but with the wider Java Dev community: although I wrote the final implementation, proposal and proof-of-concept came from people outside (Youri and Gunnar, especially!) who knew JDK Record implementation better and could help figure out a way to support this from within older JDK.- “use=DEDUCTION” for
@JsonTypeInfo
was THE OLDEST OPEN ISSUE, and a good example of an elegant solution that I hadn’t been able to figure out — but in hindsight looks almost obvious in its graceful simplicity CoercionConfig
feature is something that consists of core pieces included in 2.12, but does require support from individualJsonDeserializer
implementations — and although some modules were retrofitted (esp. Joda and Java 8 date/time), more work remains to properly check various coercions extension modules offer.@JsonIncludeProperties
is probably the Single Most Requested feature, ever, and will probably both (a) be very widely used and (b) lead to users finding edge cases not handled well (wrt combination of various visibility and inclusion settings)
Other Misc Features, Improvements
Aside from the “big five”, there are many other notable fixes, features and improvements (after all, there were more than 100 resolved issues). Here’s a sampling:
- #1458:
@JsonAnyGetter
on (Map-valued) Field (not just getter method) - #2215:
BigDecimal
,BigInteger
Creator auto-detection (beyondint
,long
,boolean
,String
) - #2675:
Void
-valued properties (MapperFeature.ALLOW_VOID_VALUED_PROPERTIES) — note, notvoid
but specifically “wrapper” type equivalent, used in some case by Kotlin, Scala for “Nothing” type - #2683: Explicit fail for
java.time.*
types if no (de)serializer provided by a module (to try to resolve problem of users accidentally serializing Java 8 Date/Time values as POJOs which cannot be deserialized back). - #2776: Explicit fail for
org.joda.time.*
types (see above; try to avoid users serializing Joda date/time types as POJOs) - #2871:
@JsonKey
annotation: similar to@JsonValue
but used (only) for serialization as Map key - #2885: Add
JsonNode.canConvertToExactIntegral()
to see if a floating-point value happens to only have integral part (like25.00
) and could be converted into integral type without loss of information
Plans for 2.13
With so much included in 2.12, is there anything left to do for 2.13 and beyond?
Ok just joking. :)
Even 2.12 left out couple of things that I originally hoped to tackle — for example, JsonNodeFeature
(outlined in JSTEP-3) and possible @PreSerialize
and/or @PostDeserialize
method annotations (see databind#2045), both considered Most-Wanted features.
So what am I thinking about immediate future of Jackson development?
- First things first: blog more about 2.12 (more in-detail deep(er) dive); and more about Jackson in general — maybe “24 blog posts in December 2020”? (remember to Clap! Author craves attention! :) )
- Focus should finally shift back to Jackson 3.0 work, some of which is outlined in JSTEP-1. In particular, work on changing
JsonProcessingException
into uncheckedJacksonException
(see JSTEP-4). So the start of significant non-bugfix work towards 2.13 is likely to be delayed by a couple of months (maybe Feb/Mar 2020) - … but try to keep 6–12 month release cadence for 2.x. This might mean a somewhat “smaller” 2.13, which would also nicely keep big/small/big/small rhythm, help stabilize things too, let downstream systems catch up with newer versions (and similarly for modules like Kotlin and Scala module to add support for latest databind functionality)
- Propose Java 8 baseline for all modules that currently only require JDK 7 (but likely keep Java 6 for annotations [no need to upgrade] and streaming API (similarly limited upside))
- Work on 2 “missing” features from 2.12 (JsonNodeFeature; post-deser/pre-ser hooks)
- Figure out how to support “Logical Types” — something both Avro and CBOR expose, but that is difficult to expose via streaming API currently (since logical types are more of databinding concept — but exposed at low level via codecs for Avro and CBOR both)
- Design a way to add Processing Limits; maximum size/complexity settings for documents, to protect against potential Denial-of-Service attacks (some feature request already exist, but need a general enough API approach — maybe something similar to what Woodstox did with XML processing limits)
- Protobuf 3 support? Protobuf module could use some TLC overall