37 runnable examples covering the eight feature posts on ankurm.com, verified against Jackson 3.2.1 on Temurin 21.0.5. Every output committed under docs/ was produced by run-all.sh. Also documents 11 places where the published snippets do not compile or do not behave as printed against a real Jackson 3 build - most notably that writeValueAsString(List<Base>) silently drops the polymorphic type discriminator, so the post's serialised output cannot be read back.
3.2 KiB
Part 4 — Custom serialisers, deserialisers and mix-ins
Post: https://ankurm.com/jackson-custom-serializer-mixin/
This is the part with the most API churn. The post's code is Jackson 2 throughout.
| Jackson 2 | Jackson 3 |
|---|---|
JsonSerializer<T> |
ValueSerializer<T> |
JsonDeserializer<T> |
ValueDeserializer<T> |
SerializerProvider |
SerializationContext |
com.fasterxml.jackson.databind.ser.std.StdSerializer |
tools.jackson.databind.ser.std.StdSerializer |
gen.writeNumberField / writeStringField |
gen.writeNumberProperty / writeStringProperty |
p.getCodec().readTree(p) |
ctxt.readTree(p) |
throws IOException |
removed — exceptions are unchecked |
new SimpleModule(name, Version) |
new SimpleModule(name) |
mapper.registerModule(m) |
builder.addModule(m) |
mapper.addMixIn(a, b) |
builder.addMixIn(a, b) |
E01 / E02 — the handlers
E01MoneyValueSerializer.java ·
E02MoneyValueDeserializer.java ·
Money.java
The deserialiser uses path() rather than get(). The post's version calls
rootNode.get("amount").decimalValue(), which throws NullPointerException on
{"currency":"USD"}. There is a second trap: in Jackson 3, a bare decimalValue() on
a MissingNode throws, where Jackson 2 returned BigDecimal.ZERO. The defaulting
overload decimalValue(BigDecimal.ZERO) is what you want.
E03 — registration
E03SimpleModuleRegistration.java
serialised : {"amount":20.00,"currency":"USD"}
amount : 20.0 (scale lost)
amount exact : 20.00 (scale preserved)
missing field: Money[amount=0, currencyCode=EUR]
no module : {"amount":19.999,"currencyCode":"usd"}
The post prints 20.00 for the deserialised amount. You get 20.0 — Jackson parses the
literal as a double before handing it over, so the scale is gone. If scale matters, and
for money it does, enable USE_BIG_DECIMAL_FOR_FLOATS, as the third line shows.
E04 — mix-ins
with mixin : {"city":"Springfield","street":"123 Main St","zip":"12345"}
without mixin: {"city":"Springfield","internalTrackingCode":"INTERNAL-X99","postalCode":"12345","street":"123 Main St"}
The second line is the proof that nothing was modified at the bytecode level: a mapper
built without the mix-in still sees internalTrackingCode and postalCode.
E05 — ValueSerializer directly
Beyond the post. StdSerializer kept its name, which hides the rename; extending
ValueSerializer directly makes it obvious, and is the leaner form for a simple wrapper.
custom : {"assignee":"u-42","title":"Fix build"}
default : {"assignee":{"value":"u-42"},"title":"Fix build"}
base class: tools.jackson.databind.ValueSerializer