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.
This commit is contained in:
123
README.md
Normal file
123
README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# jackson3-by-example
|
||||
|
||||
Runnable companion code for the eight-part **Jackson 3** series on [ankurm.com](https://ankurm.com).
|
||||
|
||||
Every example is a standalone `main()` you can run on its own. Every line of output
|
||||
in [`docs/`](docs) was produced by [`run-all.sh`](run-all.sh) on the versions below —
|
||||
nothing is transcribed by hand.
|
||||
|
||||
```
|
||||
Jackson 3.2.1 (tools.jackson.core:jackson-databind)
|
||||
jackson-annotations 2.22 (com.fasterxml.jackson.core — deliberately unchanged)
|
||||
JDK Temurin 21.0.5
|
||||
Maven 3.9.9
|
||||
```
|
||||
|
||||
## Run it
|
||||
|
||||
```bash
|
||||
git clone https://ankurm.com/git.app/asmhatre/jackson3-by-example.git
|
||||
cd jackson3-by-example
|
||||
./run-all.sh # compiles, runs all 37 examples, refreshes docs/output/
|
||||
```
|
||||
|
||||
Or run any single example:
|
||||
|
||||
```bash
|
||||
mvn -q compile
|
||||
mvn -q exec:java -Dexec.mainClass=com.ankurm.jackson3.part5polymorphic.F01SerialiseMixedList
|
||||
```
|
||||
|
||||
## Map: post → code
|
||||
|
||||
| Post | Package | Examples | Notes & output |
|
||||
|---|---|---|---|
|
||||
| [Jackson 101](https://ankurm.com/jackson-java-tutorial/) | [`part0setup`](src/main/java/com/ankurm/jackson3/part0setup) | [A01](src/main/java/com/ankurm/jackson3/part0setup/A01FirstRoundTrip.java) [A02](src/main/java/com/ankurm/jackson3/part0setup/A02SharedMapperConfiguration.java) [A03](src/main/java/com/ankurm/jackson3/part0setup/A03ThreeProcessingModels.java) | [write-up](docs/part0-setup.md) |
|
||||
| [ObjectMapper Guide](https://ankurm.com/jackson-objectmapper-guide/) | [`part1objectmapper`](src/main/java/com/ankurm/jackson3/part1objectmapper) | [B01](src/main/java/com/ankurm/jackson3/part1objectmapper/B01WriteJson.java) [B02](src/main/java/com/ankurm/jackson3/part1objectmapper/B02ReadJson.java) [B03](src/main/java/com/ankurm/jackson3/part1objectmapper/B03GenericCollections.java) [B04](src/main/java/com/ankurm/jackson3/part1objectmapper/B04PropertyOrdering.java) | [write-up](docs/part1-objectmapper.md) |
|
||||
| [Records, Optionals, Sealed](https://ankurm.com/jackson-java-records-optionals/) | [`part2modernjava`](src/main/java/com/ankurm/jackson3/part2modernjava) | [C01](src/main/java/com/ankurm/jackson3/part2modernjava/C01RecordRoundTrip.java) [C02](src/main/java/com/ankurm/jackson3/part2modernjava/C02OptionalFields.java) [C03](src/main/java/com/ankurm/jackson3/part2modernjava/C03SealedWithSubTypes.java) [C04](src/main/java/com/ankurm/jackson3/part2modernjava/C04SealedAutoDiscovery.java) | [write-up](docs/part2-modern-java.md) |
|
||||
| [Annotations Cheat Sheet](https://ankurm.com/jackson-annotations-guide/) | [`part3annotations`](src/main/java/com/ankurm/jackson3/part3annotations) | [D01](src/main/java/com/ankurm/jackson3/part3annotations/D01RenameAndIgnore.java) [D02](src/main/java/com/ankurm/jackson3/part3annotations/D02InclusionAndFormat.java) [D03](src/main/java/com/ankurm/jackson3/part3annotations/D03AliasAndUnknownFields.java) [D04](src/main/java/com/ankurm/jackson3/part3annotations/D04CreatorsAndUnwrapping.java) | [write-up](docs/part3-annotations.md) |
|
||||
| [Custom Serialisers & Mix-ins](https://ankurm.com/jackson-custom-serializer-mixin/) | [`part4custom`](src/main/java/com/ankurm/jackson3/part4custom) | [E01](src/main/java/com/ankurm/jackson3/part4custom/E01MoneyValueSerializer.java) [E02](src/main/java/com/ankurm/jackson3/part4custom/E02MoneyValueDeserializer.java) [E03](src/main/java/com/ankurm/jackson3/part4custom/E03SimpleModuleRegistration.java) [E04](src/main/java/com/ankurm/jackson3/part4custom/E04MixinAnnotations.java) [E05](src/main/java/com/ankurm/jackson3/part4custom/E05ValueSerializerDirect.java) | [write-up](docs/part4-custom.md) |
|
||||
| [Polymorphic Deserialisation](https://ankurm.com/jackson-polymorphic-deserialization/) | [`part5polymorphic`](src/main/java/com/ankurm/jackson3/part5polymorphic) | [F01](src/main/java/com/ankurm/jackson3/part5polymorphic/F01SerialiseMixedList.java) [F02](src/main/java/com/ankurm/jackson3/part5polymorphic/F02DeserialiseMixedList.java) [F03](src/main/java/com/ankurm/jackson3/part5polymorphic/F03IncludeStrategies.java) [F04](src/main/java/com/ankurm/jackson3/part5polymorphic/F04UnknownTypeId.java) | [write-up](docs/part5-polymorphic.md) |
|
||||
| [Streaming API & JsonNode](https://ankurm.com/jackson-streaming-api-jsonnode/) | [`part6streaming`](src/main/java/com/ankurm/jackson3/part6streaming) | [G01](src/main/java/com/ankurm/jackson3/part6streaming/G01StreamingParserFilter.java) [G02](src/main/java/com/ankurm/jackson3/part6streaming/G02StreamingGenerator.java) [G03](src/main/java/com/ankurm/jackson3/part6streaming/G03TreeModelNavigation.java) [G04](src/main/java/com/ankurm/jackson3/part6streaming/G04ThreeApproachesMeasured.java) | [write-up](docs/part6-streaming.md) |
|
||||
| [Security Best Practices](https://ankurm.com/jackson-security-best-practices/) | [`part7security`](src/main/java/com/ankurm/jackson3/part7security) | [H01](src/main/java/com/ankurm/jackson3/part7security/H01SafePolymorphismByAnnotation.java) [H02](src/main/java/com/ankurm/jackson3/part7security/H02DefaultTypingRemoved.java) [H03](src/main/java/com/ankurm/jackson3/part7security/H03PolymorphicTypeValidatorAllowlist.java) [H04](src/main/java/com/ankurm/jackson3/part7security/H04StreamReadConstraints.java) [H05](src/main/java/com/ankurm/jackson3/part7security/H05NeverDeserialiseIntoObject.java) | [write-up](docs/part7-security.md) |
|
||||
| — (beyond the posts) | [`beyond`](src/main/java/com/ankurm/jackson3/beyond) | [Y01](src/main/java/com/ankurm/jackson3/beyond/Y01UncheckedExceptions.java) [Y02](src/main/java/com/ankurm/jackson3/beyond/Y02TrailingTokens.java) [Y03](src/main/java/com/ankurm/jackson3/beyond/Y03DateTimeDefaults.java) [Y04](src/main/java/com/ankurm/jackson3/beyond/Y04ImmutableMapperAndReaders.java) [Y05](src/main/java/com/ankurm/jackson3/beyond/Y05CreatorDetection.java) [Y06](src/main/java/com/ankurm/jackson3/beyond/Y06RecyclerPoolTuning.java) | [write-up](docs/beyond.md) |
|
||||
|
||||
### Documentation
|
||||
|
||||
Each page pairs the code with its real captured output and calls out where the post and
|
||||
the library disagree.
|
||||
|
||||
| Page | Covers |
|
||||
|---|---|
|
||||
| [docs/part0-setup.md](docs/part0-setup.md) | Setup, the shared-mapper rule, three processing models |
|
||||
| [docs/part1-objectmapper.md](docs/part1-objectmapper.md) | Reading, writing, `TypeReference`, property ordering |
|
||||
| [docs/part2-modern-java.md](docs/part2-modern-java.md) | Records, `Optional`, sealed types and auto-discovery |
|
||||
| [docs/part3-annotations.md](docs/part3-annotations.md) | The annotation set, and which advice is now obsolete |
|
||||
| [docs/part4-custom.md](docs/part4-custom.md) | `ValueSerializer`, `ValueDeserializer`, modules, mix-ins |
|
||||
| [docs/part5-polymorphic.md](docs/part5-polymorphic.md) | `@JsonTypeInfo`, and the dropped-discriminator defect in full |
|
||||
| [docs/part6-streaming.md](docs/part6-streaming.md) | Streaming, the tree model, and the three approaches measured |
|
||||
| [docs/part7-security.md](docs/part7-security.md) | Safe polymorphism, allowlists, resource limits |
|
||||
| [docs/beyond.md](docs/beyond.md) | Unchecked exceptions, flipped defaults, pool tuning |
|
||||
|
||||
Raw stdout for all 37 programs is in [`docs/output/`](docs/output), regenerated by
|
||||
[`run-all.sh`](run-all.sh).
|
||||
|
||||
## Corrections to the posts
|
||||
|
||||
Writing this code against a real Jackson 3.2.1 build surfaced places where the blog
|
||||
snippets do not compile or do not behave as printed. Each is demonstrated by a program,
|
||||
so you can verify rather than take my word for it.
|
||||
|
||||
| # | Claim in the post | What actually happens | Shown by |
|
||||
|---|---|---|---|
|
||||
| 1 | Maven coordinate `com.fasterxml.jackson.core:jackson-databind:3.1.2` | That artifact does not exist. Jackson 3 is at `tools.jackson.core:jackson-databind`; only `jackson-annotations` keeps the old group ID. | [`pom.xml`](pom.xml) |
|
||||
| 2 | `JsonMapper.builder().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)` | Does not compile. The constant is not on `SerializationFeature` in Jackson 3 — it moved to `tools.jackson.databind.cfg.DateTimeFeature`, and it already defaults to off, so ISO-8601 needs no configuration. | [Y03](src/main/java/com/ankurm/jackson3/beyond/Y03DateTimeDefaults.java) |
|
||||
| 3 | `JsonMapper.builder().serializationInclusion(...)` | No such builder method. The real API is `changeDefaultPropertyInclusion(UnaryOperator)`. | [A02](src/main/java/com/ankurm/jackson3/part0setup/A02SharedMapperConfiguration.java), [H02](src/main/java/com/ankurm/jackson3/part7security/H02DefaultTypingRemoved.java) |
|
||||
| 4 | `writeValueAsString(List<PaymentMethod>)` emits `paymentType` | It does not. A `List` carries no element type, so the polymorphic serialiser never engages and the discriminator is dropped — and the output then fails to deserialise. Use `writerFor(TypeReference)` or a typed array. | [F01](src/main/java/com/ankurm/jackson3/part5polymorphic/F01SerialiseMixedList.java), [F02](src/main/java/com/ankurm/jackson3/part5polymorphic/F02DeserialiseMixedList.java) |
|
||||
| 5 | Remediate with `mapper.activateDefaultTyping(validator, ...)` | Jackson 3's mapper has no mutators at all — not `enableDefaultTyping`, not `activateDefaultTyping`, not one `set*` method. `activateDefaultTyping` exists only on `JsonMapper.Builder`. | [H02](src/main/java/com/ankurm/jackson3/part7security/H02DefaultTypingRemoved.java), [H03](src/main/java/com/ankurm/jackson3/part7security/H03PolymorphicTypeValidatorAllowlist.java) |
|
||||
| 6 | `import tools.jackson.core.JsonFactory` | Wrong package. It is `tools.jackson.core.json.JsonFactory`. | [G01](src/main/java/com/ankurm/jackson3/part6streaming/G01StreamingParserFilter.java) |
|
||||
| 7 | Optional needs `jackson-datatype-jdk8`; records need `jackson-module-parameter-names` and `-parameters` | All three are built into `jackson-databind` 3.x. The Jackson 2 module classes do not exist under `tools.jackson`, so leaving the registrations in place is a compile error. | [C01](src/main/java/com/ankurm/jackson3/part2modernjava/C01RecordRoundTrip.java), [C02](src/main/java/com/ankurm/jackson3/part2modernjava/C02OptionalFields.java) |
|
||||
| 8 | Set `FAIL_ON_UNKNOWN_PROPERTIES=false`; use `@JsonIgnoreProperties(ignoreUnknown=true)` to opt out per class | Already false by default in Jackson 3. The annotation now matters only when you deliberately turn strictness back on. | [D03](src/main/java/com/ankurm/jackson3/part3annotations/D03AliasAndUnknownFields.java) |
|
||||
| 9 | Without `@JsonFormat`, `LocalDate` is written as a numeric array | Jackson 2 behaviour. Jackson 3 writes ISO-8601 by default; the annotation is only needed for a non-standard pattern. | [D02](src/main/java/com/ankurm/jackson3/part3annotations/D02InclusionAndFormat.java) |
|
||||
| 10 | Removing `AUTO_DETECT_CREATORS` means single-arg constructors "quietly fail" | The enum constant is gone, but the behaviour is not: a single-argument constructor is still detected as a delegating creator. Annotate anyway, but the upgrade will not break these classes. | [Y05](src/main/java/com/ankurm/jackson3/beyond/Y05CreatorDetection.java) |
|
||||
| 11 | The custom deserialiser uses `p.getCodec().readTree(p)` and `node.get(...)` | `getCodec()` is gone — use `ctxt.readTree(parser)`. And `get()` NPEs on a missing field; `path()` with a defaulting accessor does not. A bare `decimalValue()` on a `MissingNode` throws in Jackson 3 where Jackson 2 returned zero. | [E02](src/main/java/com/ankurm/jackson3/part4custom/E02MoneyValueDeserializer.java), [E03](src/main/java/com/ankurm/jackson3/part4custom/E03SimpleModuleRegistration.java) |
|
||||
|
||||
Two smaller ones worth knowing, neither strictly an error in the posts:
|
||||
|
||||
- A getter-based POJO serialises its properties **alphabetically**; a record serialises in
|
||||
declaration order. The post's sample output for `ProductSummary` shows declaration order.
|
||||
See [B04](src/main/java/com/ankurm/jackson3/part1objectmapper/B04PropertyOrdering.java).
|
||||
- `{"amount":20.00}` deserialises to `BigDecimal` **20.0**, not 20.00 — the scale is lost
|
||||
unless `USE_BIG_DECIMAL_FOR_FLOATS` is enabled. See
|
||||
[E03](src/main/java/com/ankurm/jackson3/part4custom/E03SimpleModuleRegistration.java).
|
||||
|
||||
## Measured, not asserted
|
||||
|
||||
Three examples produce numbers rather than prose. Figures below are from one run on a
|
||||
2-core container; re-run them on your own hardware.
|
||||
|
||||
**Three processing models over a 20 MB / 200k-entry file** ([G04](src/main/java/com/ankurm/jackson3/part6streaming/G04ThreeApproachesMeasured.java)):
|
||||
|
||||
```
|
||||
data binding (readValue) errors=4000 524 ms heap delta 47 MB
|
||||
tree model (readTree) errors=4000 341 ms heap delta 102 MB
|
||||
streaming (JsonParser) errors=4000 78 ms heap delta 1 MB
|
||||
```
|
||||
|
||||
**Streaming a million records** ([G02](src/main/java/com/ankurm/jackson3/part6streaming/G02StreamingGenerator.java)): 30 MB written in 129 ms with a 0 MB heap delta.
|
||||
|
||||
**RecyclerPool** ([Y06](src/main/java/com/ankurm/jackson3/beyond/Y06RecyclerPoolTuning.java)) — the comparison post suggests restoring the 2.x
|
||||
thread-local pool if you see a regression. On this box the two pools are within noise of
|
||||
each other in both shapes, and the gap moves between runs, so the honest conclusion is
|
||||
that there is no default winner: measure on your own hardware before changing it. What
|
||||
is stable across runs is that turning recycling off entirely is clearly worse — a useful
|
||||
negative control confirming the pool is doing something.
|
||||
|
||||
```
|
||||
1 thread threadLocalPool 301 ms concurrentDeque 310 ms nonRecycling 496 ms
|
||||
8 threads threadLocalPool 451 ms concurrentDeque 450 ms nonRecycling 808 ms
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [jackson2-to-3-migration](https://ankurm.com/git.app/asmhatre/jackson2-to-3-migration) — the before/after companion for the two migration guides.
|
||||
Reference in New Issue
Block a user