Skip to content

Commit e9cdaa7

Browse files
author
Tom Regan
committed
Add Using Jackson Deduction to Simplify Deserialisation post
1 parent e7ada43 commit e9cdaa7

File tree

5 files changed

+136
-3
lines changed

5 files changed

+136
-3
lines changed

.github/workflows/hugo.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ jobs:
4040
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
4141
- name: Install Dart Sass
4242
run: sudo snap install dart-sass
43+
- name: Install ruby
44+
uses: ruby/setup-ruby@v1
45+
with:
46+
ruby-version: 2.7
47+
- name: Install Asciidoctor
48+
uses: reitzig/actions-asciidoctor@v2.0.1
49+
with:
50+
version: 2.0.18
4351
- name: Checkout
4452
uses: actions/checkout@v3
4553
with:
@@ -75,4 +83,4 @@ jobs:
7583
steps:
7684
- name: Deploy to GitHub Pages
7785
id: deployment
78-
uses: actions/deploy-pages@v2
86+
uses: actions/deploy-pages@v2

config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ menu:
2727
name: Categories
2828
url: /categories/
2929
weight: 10
30+
31+
security:
32+
exec:
33+
allow:
34+
- ^(dart-)?sass(-embedded)?$
35+
- ^go$
36+
- ^npx$
37+
- ^postcss$
38+
- ^asciidoctor$
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
date: 2024-05-30
3+
title: Using Jackson Deduction to Simplify Deserialisation
4+
cover:
5+
image: posts/2024-05-30-using-jackson-deduction-to-simplify-deserialisation/cover.jpeg
6+
tags:
7+
- java
8+
- jackson
9+
---
10+
11+
I find the documentation for Jackson is on the terse side, and dare I say
12+
obstructively self-referential. If you aren't moved fully to tears by the Javadoc
13+
entries for `JsonTypeInfo.As`, you deserve an ACM award.
14+
15+
In short: I need a helpful and up-to-date guide, so I'm writing one.
16+
17+
== What are we trying to do?
18+
19+
We often want to vary the content of our JSON. It's integral to the https://www.enterpriseintegrationpatterns.com/patterns/messaging/EnvelopeWrapper.html[Envelope pattern] for instance. Varying the content looks like this.
20+
21+
Here's an example of an intergalactic animal.
22+
23+
[source,json]
24+
----
25+
{
26+
"created": "1977-05-25T12:00:00Z",
27+
"animal": {
28+
"galaxy": "Far Far Away",
29+
"name": "Womp Rat"
30+
}
31+
}
32+
----
33+
34+
And here's an example of a provincial animal.
35+
36+
[source,json]
37+
----
38+
{
39+
"created": "1835-09-15T06:00:00Z",
40+
"animal": {
41+
"province": "Galápagos Islands",
42+
"name": "Galápagos tortoise"
43+
}
44+
}
45+
----
46+
47+
In our Java code, we want to be able to deserialise Animals, and we want to handle either `IntergalacticAnimals`,
48+
or `ProvincialAnimals`. We might write some transport code that looks like this.
49+
50+
[source,java]
51+
----
52+
record AnimalSpottedEvent<A extends Animal>(Instant created, A animal) {}
53+
54+
interface Animal {}
55+
56+
record IntergalacticAnimal(String galaxy, String name) implements Animal {}
57+
58+
record ProvincialAnimal(String province, String name) implements Animal {}
59+
----
60+
61+
When it's called to handle deserialisation of an Animal over the wire, Jackson needs to work out
62+
what's the "message inside the envelope?" Is it an `IntergalacticAnimal`, or is it a `ProvincialAnimal`?
63+
Without this knowledge, Jackson can't instantiate the right kind of object.
64+
65+
There is a (truly excellent) resource, https://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html[Deserialize JSON with Jackson into Polymorphic Types - A Complete Example],
66+
which describes how to do this using `JsonTypeInfo`, but it was written in 2011 for Jackson 1.7.
67+
68+
== State of the Art
69+
70+
You may have missed the 2.12.0 release of Jackson Databind in Novemeber 2020. The world was
71+
generally busy with other things around then. In which case the https://fasterxml.github.io/jackson-annotations/javadoc/2.12/com/fasterxml/jackson/annotation/JsonTypeInfo.Id.html#DEDUCTION[`JsonTypeInfo.Id#DEDUCTION`]
72+
could have slipped by you unnoticed.
73+
74+
The newer "deduction" mode takes away the need to tell Jackson _how_ to read the incoming data; instead
75+
Jackson will take a list of possible choices you give it, and it will determine the best match for
76+
deserialisation.
77+
78+
What does that look like? You can follow these steps:
79+
80+
1. add `@JsonTypeInfo(use = DEDUCTION)` to the envelope property
81+
2. add `@JsonSubTypes(@Type(...))`
82+
83+
Here is our updated example code.
84+
85+
[source,java]
86+
----
87+
record AnimalSpottedEvent<A extends Animal>(
88+
Instant created,
89+
@JsonTypeInfo(use = DEDUCTION)
90+
@JsonSubTypes({
91+
@Type(IntergalacticAnimal.class),
92+
@Type(ProvincialAnimal.class)})
93+
A animal) {}
94+
95+
interface Animal {}
96+
97+
record IntergalacticAnimal(String galaxy, String name) implements Animal {}
98+
99+
record ProvincialAnimal(String province, String name) implements Animal {}
100+
----
101+
102+
https://github.com/TomRegan/jackson-deduction-demo[I've created a demo project] as a companion to provide a working example.
103+
104+
Happy deserialisation!
105+
106+
== Appendix
107+
108+
=== Dependencies
109+
110+
`jackson-annotations` provides `@JsonTypeInfo` and associated annotations which you'll use in your transport code.
111+
112+
`jackson-databind` provides the "invisible" deserialisers. You'll need these available on the classpath in your server
113+
or client application.
114+
115+
There are any number of ways to get `jackson-annotations` and `jackson-databind` on your classpath. `spring-boot-starter-web`
116+
includes many Jackson components; alternatively the Jackson project provides a https://mvnrepository.com/artifact/com.fasterxml.jackson/jackson-bom[BOM].

content/posts/code-shame.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ when I came back to change the code. This is something I know inside-out,
1414
so the point of this post is to shame me into never making the same mistake
1515
again.
1616

17-
{% highlight diff %}
17+
```diff
1818
--- a/src/resource_handler.c
1919
+++ b/src/resource_handler.c
2020
@@ -27,16 +27,18 @@ uint16_t
@@ -45,7 +45,7 @@ service_request(struct request *req, char *rtrv_buf, const size_t len)
4545
}
4646

4747
return RINTERNAL;
48-
{% endhighlight %}
48+
```
4949

5050
In the original code I'm deferencing a field in a struct (`req->method`)
5151
without first finding out whether the struct had been assigned.

0 commit comments

Comments
 (0)