1
0
Files
jackson3-by-example/docs/part4-custom.md
Ankur c438afc33b Jackson 3 series companion code
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.
2026-08-04 23:29:26 +05:30

73 lines
3.2 KiB
Markdown

# 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`](../src/main/java/com/ankurm/jackson3/part4custom/E01MoneyValueSerializer.java) ·
[`E02MoneyValueDeserializer.java`](../src/main/java/com/ankurm/jackson3/part4custom/E02MoneyValueDeserializer.java) ·
[`Money.java`](../src/main/java/com/ankurm/jackson3/part4custom/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`](../src/main/java/com/ankurm/jackson3/part4custom/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
[`E04MixinAnnotations.java`](../src/main/java/com/ankurm/jackson3/part4custom/E04MixinAnnotations.java)
```
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
[`E05ValueSerializerDirect.java`](../src/main/java/com/ankurm/jackson3/part4custom/E05ValueSerializerDirect.java)
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
```