Kotlin: a pitfall in JSON serialization library

Jeff Li
3 min readMay 25, 2021

Kotlin serialization library has been released for a while. As for serialization, the most common use case is to do conversion between JSON format string and objects. Like, we convert JSON format string returned by HTTP requests to objects of predefined classes or encode an object to JSON string.

However, after playing around with the serialization library, I found some unexpected and confusing behaviors in the JSON serialization process. Let's go straight to the point.

Environment:

"jvm" version "1.5.0"
"plugin.serialization" version "1.5.0"
org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1

The library introduces many features for JSON serialization. One of them is “Default values are not encoded by default in JSON”. Here is the example:

Output:

{“id”:0}

As we can see, the user’s name is not encoded in the output cos it has a default value. The rationale behind this design given by the official guide is “in most real-life scenarios such configuration reduces visual clutter and saves the amount of data being serialized.”

It makes sense and nothing is wrong here. Let's take it a step further.

Assuming the name is dynamically calculated with the user’s id:

Output:

{“id”:0}

The result is consistent with the specification and everything looks good for now. But how many times is the caclulateName called?

From the perspective of developers, we are likely to expect the calculateName is called only once cos we only create one User object during this whole process. So it should be called only when User‘s constructor is called. Let's modify the code a bit:

Output:

calculating name of 0
calculating name of 0
{“id”:0}

Oops, surprise! What’s going on here?

In serializing process, the object relies on itsserializer generated by a compiler plugin at compile time.

So in order to find the truth, we can decompile the User class to check the mechanism hidden from us.

The problem is caused by the line circled. shouldEncodeElementDefault will return false. The reason is Json we are using is a global instance of Json class that is created according to the default configuration.

In the default configuration, default values are ignored, just like what the official specification describes.

Then serializer simply calls calculateName again so as to test if its value equals the default one.

Finally, we know where the problem is.

The duplicate call can potentially lead to some serious problems. Like, some variables can only be initialized once, two same requests sent accidentally, and etc.

When it comes to a solution, the library can add a placeholder in class through the serialization compiler plugin for storing the value calculated in its constructor so that additional calculateName call can be avoided.

However, in my opinion, it could be better to prevent users from calculating default values dynamically in JSON serialization. It is more natural to consider default values as something constant which can be obtained at compile time. As a new language, we tend to add more features to make us more attractive. However, sometimes, it is also a type of wisdom to do subtraction.

--

--