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<Pers
on>
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.