Peculiar Java JSON serializer problems

Finally, in my Semantic Web developer career, I got to that point when I had to do some work with JSON in Java. And as it goes, typically one needs to read some JSON, from an API, and make it available in a POJO (deserialize) and vice-versa (serialize).
The only requirement I had, was to avoid using annotations of any kind, such as @JsonProperty or @JsonDeserialize. Let’s take as an example a JSON object describing a person:

Please notice the @name property and that the degree property is a nested JSON object which can contain a different number of sub-properties, not known in advance.

First, we deserialize the above JSON into a Person POJO, then some data gets changed, and then it gets serialized back to a JSON object.

The POJO of the above example JSON is called Person.java and looks like this:

The focus is now on the serializing of this POJO.

Since I could not use annotations (@JsonProperty or @JsonDeserialize), I had to write a custom serializer or adapter (depending on which JSON library is used). This is where the peculiarities started.

My solution is written for both libraries: Jackson and JSON-B with Yasson. Next, I will exemplify them.

Let’s start with Jackson.

I pulled in Jackson version 2.13.4 from Maven and started with a Custom Serializer. The custom serializer needs to override the serialize method of the extended StdSerializer<Person> class. In the serialize method is where the customization happens:

Above, I am attempting to write the degree property, which in the POJO is of type Map<String, Object>, out as a string. This ends up being serialized as a JSON as follows:

did solve the @name property but the degree property is quite wrong! It is a String.

So how can this be improved with Jackson?

The solution is to use writePOJOField(). The exact and correct code looks like follows:

Awesome! Solved it for Jackson. Find the full code on GitHub.

Let’s see the solution also using JSON-B.

I have the same requirement: to not use any annotations. For this, I pulled in the JSON-B 1.0.2 version from Maven central and Yasson 1.0.3.

In the case of JSON-B, I needed to write an adapter to deal with the @name property. As goes, the adapter needs to override the adaptToJson (serialize) and adaptFromJson (deserialize) methods from the JsonbAdapter<Person, JsonObject> interface. So, the first go at it looks like the following (and I only focus on the adaptToJson method here):

If we take a look at the serialized JSON, we have again the same problem as with Jackson. The @name property is ok but the degree property is wrong again! The generated JSON looks like follows:

So how can this be done any better?
While in Jackson we had the brilliant writePOJOField method, JSON-B does not have such a method. My idea was for the adaptToJson method to use a helper method called addRightJsonType. So the correct adaptToJson is:

And the helper method addRightJsonType is a recursive method, that tries to catch all possible types of the entry value and deal with it accordingly:

Find the complete code on GitHub, which includes also the code for adaptFromJson method.

Conclusion

When I set out to serialize JSON in Java, little did I know that this task will come with some peculiarities. Writing a Jackson serializer or a JSON-B adapter means one needs to specify in detail how each property is to be handled. There are no shortcuts like using .toString() on a Map<String, Object> degree property. For some reason, I initially thought that by simply omitting it completely, it would still magically know how to deal with it 😅.

I also see how Jackson, by having a dedicated writePOJOField method, can be considered the most mature Java JSON parsing library. I also say this, because while searching online for solutions, other libraries also had the same problems as Yasson: no dedicated method to do this simple task.

Remark: for code simplicity, some if statements were omitted in the above code. See the full code on GitHub.