diff --git a/.github/workflows/hugo.yml b/.github/workflows/hugo.yml index f3be329..840a7e6 100644 --- a/.github/workflows/hugo.yml +++ b/.github/workflows/hugo.yml @@ -40,6 +40,14 @@ jobs: && sudo dpkg -i ${{ runner.temp }}/hugo.deb - name: Install Dart Sass run: sudo snap install dart-sass + - name: Install ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + - name: Install Asciidoctor + uses: reitzig/actions-asciidoctor@v2.0.1 + with: + version: 2.0.18 - name: Checkout uses: actions/checkout@v3 with: @@ -75,4 +83,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 \ No newline at end of file + uses: actions/deploy-pages@v2 diff --git a/config.yml b/config.yml index a1b4850..74a087e 100644 --- a/config.yml +++ b/config.yml @@ -27,3 +27,12 @@ menu: name: Categories url: /categories/ weight: 10 + +security: + exec: + allow: + - ^(dart-)?sass(-embedded)?$ + - ^go$ + - ^npx$ + - ^postcss$ + - ^asciidoctor$ diff --git a/content/posts/maven-build-tips.md b/content/posts/2023-04-25-quick-repeatable-maintainable-maven-builds/maven-build-tips.md similarity index 100% rename from content/posts/maven-build-tips.md rename to content/posts/2023-04-25-quick-repeatable-maintainable-maven-builds/maven-build-tips.md diff --git a/content/posts/2024-05-30-using-jackson-deduction-to-simplify-deserialisation/using-jackson-deduction-to-simplify-deserialisation.adoc b/content/posts/2024-05-30-using-jackson-deduction-to-simplify-deserialisation/using-jackson-deduction-to-simplify-deserialisation.adoc new file mode 100644 index 0000000..d599204 --- /dev/null +++ b/content/posts/2024-05-30-using-jackson-deduction-to-simplify-deserialisation/using-jackson-deduction-to-simplify-deserialisation.adoc @@ -0,0 +1,116 @@ +--- +date: 2024-05-30 +title: Using Jackson Deduction to Simplify Deserialisation +cover: + image: posts/2024-05-30-using-jackson-deduction-to-simplify-deserialisation/cover.jpeg +tags: +- java +- jackson +--- + +I find the documentation for Jackson is on the terse side, and dare I say +obstructively self-referential. If you aren't moved fully to tears by the Javadoc +entries for `JsonTypeInfo.As`, you deserve an ACM award. + +In short: I need a helpful and up-to-date guide, so I'm writing one. + +== What are we trying to do? + +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. + +Here's an example of an intergalactic animal. + +[source,json] +---- +{ + "created": "1977-05-25T12:00:00Z", + "animal": { + "galaxy": "Far Far Away", + "name": "Womp Rat" + } +} +---- + +And here's an example of a provincial animal. + +[source,json] +---- +{ + "created": "1835-09-15T06:00:00Z", + "animal": { + "province": "Galápagos Islands", + "name": "Galápagos tortoise" + } +} +---- + +In our Java code, we want to be able to deserialise Animals, and we want to handle either `IntergalacticAnimals`, +or `ProvincialAnimals`. We might write some transport code that looks like this. + +[source,java] +---- +record AnimalSpottedEvent(Instant created, A animal) {} + +interface Animal {} + +record IntergalacticAnimal(String galaxy, String name) implements Animal {} + +record ProvincialAnimal(String province, String name) implements Animal {} +---- + +When it's called to handle deserialisation of an Animal over the wire, Jackson needs to work out +what's the "message inside the envelope?" Is it an `IntergalacticAnimal`, or is it a `ProvincialAnimal`? +Without this knowledge, Jackson can't instantiate the right kind of object. + +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], +which describes how to do this using `JsonTypeInfo`, but it was written in 2011 for Jackson 1.7. + +== State of the Art + +You may have missed the 2.12.0 release of Jackson Databind in Novemeber 2020. The world was +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`] +could have slipped by you unnoticed. + +The newer "deduction" mode takes away the need to tell Jackson _how_ to read the incoming data; instead +Jackson will take a list of possible choices you give it, and it will determine the best match for +deserialisation. + +What does that look like? You can follow these steps: + +1. add `@JsonTypeInfo(use = DEDUCTION)` to the envelope property +2. add `@JsonSubTypes(@Type(...))` + +Here is our updated example code. + +[source,java] +---- +record AnimalSpottedEvent( + Instant created, + @JsonTypeInfo(use = DEDUCTION) + @JsonSubTypes({ + @Type(IntergalacticAnimal.class), + @Type(ProvincialAnimal.class)}) + A animal) {} + +interface Animal {} + +record IntergalacticAnimal(String galaxy, String name) implements Animal {} + +record ProvincialAnimal(String province, String name) implements Animal {} +---- + +https://github.com/TomRegan/jackson-deduction-demo[I've created a demo project] as a companion to provide a working example. + +Happy deserialisation! + +== Appendix + +=== Dependencies + +`jackson-annotations` provides `@JsonTypeInfo` and associated annotations which you'll use in your transport code. + +`jackson-databind` provides the "invisible" deserialisers. You'll need these available on the classpath in your server +or client application. + +There are any number of ways to get `jackson-annotations` and `jackson-databind` on your classpath. `spring-boot-starter-web` +includes many Jackson components; alternatively the Jackson project provides a https://mvnrepository.com/artifact/com.fasterxml.jackson/jackson-bom[BOM]. diff --git a/content/posts/code-shame.md b/content/posts/code-shame.md index ac66503..6418d5d 100644 --- a/content/posts/code-shame.md +++ b/content/posts/code-shame.md @@ -14,7 +14,7 @@ when I came back to change the code. This is something I know inside-out, so the point of this post is to shame me into never making the same mistake again. -{% highlight diff %} +```diff --- a/src/resource_handler.c +++ b/src/resource_handler.c @@ -27,16 +27,18 @@ uint16_t @@ -45,7 +45,7 @@ service_request(struct request *req, char *rtrv_buf, const size_t len) } return RINTERNAL; -{% endhighlight %} +``` In the original code I'm deferencing a field in a struct (`req->method`) without first finding out whether the struct had been assigned.