Koson : a Kotlin DSL to write and serialize JSON

koson logo
Category: 
July 13, 2020

The Scene

When you write code for distributed apps, especially in the all mighty microservices era, there is one word that keeps showing up during technical conceptions: format.


"What human readable format will our services use to exchange messages?"


"What scheme will we be using to provide the standard output with logs?"


Very often nowadays, the answer lies within four letters: JSON (JavaScript Object Notation). Although there are other human readable alternatives (yaml is less verbose, but indentation sensitive; XML is more verbose etc.) more and more people tend to suggest (and use) JSON.


Our technical stack, as far as the JVM is concerned, is now completely implemented with Kotlin+SpringBoot services. These services produce their logs according to a specific format, that is described using JSON. They also use JSON to serialize and deserialize outgoing and incoming messages. These messages are then also validated using JSON schemas.


Long story short, JSON has become key to how we communicate, manage logs, provide and consume APIs to/from different teams.


The Problem

We all tend to like things simple. However when you are not in a JavaScript first class citizen technical stack, it can be tedious to always have to write classes just for the sole purpose of serializing and deserializing JSON. You very often have to rely on a serialization library like Jackson. To add insult to injury, sometimes when writting unit tests, you just want to provide a method, an endpoint, a BDD test with a simple JSON object.


What are your choices if you want to avoid relying on a library or a framework?


You could always use Kotlin 's multiline Strings to format your objects, especially since there is a pretty good integration in IntelliJ IDE. You could even inject language reference (Alt+Enter → Inject language or reference → JSON) and would benefit from some coloration/completion while creating your object.


"""{
    "cat": "meow",
    "dog": "woof"
}""".trimIndent()


But ultimately, every developer has to remember there is such a functionality in their IDE to benefit from these tools. However, if you want to play a bit more with formatting and still keep the code maintainance easy for future developers, this is not ideal. You can then decide to switch to a trimMargin() version.


"""
  |{
  |  "cat": "meow",
  |  "dog": "woof"
  |}
""".trimMargin()


But then JSON language injection would be lost because of the introduced | chars.


These solutions may suit you perfectly. But keeping up with upcoming code change while keeping indentation the way you want can be tricky.


The Idea

It all started from a rather simple observation: Kotlin internal DSLs allow its users to design APIs that fit well with the language, obviously.


But other interesting aspects arise. Since Kotlin allows for a very good interop with Java, it is also possible to wrap existing libraries into a nicer looking Kotlin hierarchical syntax, or even use the language as core descriptor for a build tool, such as Gradle 's Kotlin DSL.


The advantages are numerous: you’ll get code completion & formatting for free while benefiting from the compiler’s type checks.


Without digging too deep into Kotlin 's design, DSLs heavily rely on these 5 language specificities :



Koson was created using these functionalities only. No other third party libraries are used, resulting in a very lightweight Kotlin library.


Koson

Koson was open sourced under the MIT licence in 2018. Some functionalities have been added over the years, but mainly, this is how you use it:


Koson


obj and arr are the main entry points of the library, allowing to start describing your JSON content.


Here is another example:


val obj = obj {
  "key" to 3.4
  "anotherKey" to arr["test", "test2", 1, 2.433, true]
  "nullValue" to null
  "emptyObject" to obj { }
  "emptyArray" to arr
  "custom" to ZonedDateTime.now()
}

println(obj) <1>
println(obj.pretty()) <2>


<1> Will output a single line formatted JSON String:


{"key":3.4,"anotherKey":["test","test2",1,2.433,true],"nullValue":null,"emptyObject":{},"emptyArray":[],"custom":"2020-07-15T10:59:19.042965+02:00[Europe/Paris]"}


<2> Will output a 2 whitespaces indented JSON String:


{
  "key": 3.4,
  "anotherKey": [
    "test",
    "test2",
    1,
    2.433,
    true
  ],
  "nullValue": null,
  "emptyObject": {

  },
  "emptyArray": [

  ],
  "custom": "2020-07-15T10:59:19.042965+02:00[Europe/Paris]"
}


Now the good thing is you will benefit from syntax highlighting, code completion, formatting and even type safety.


For instance the library prevents errors at compile time:


https://github.com/lectra-tech/koson/raw/master/image/koson-typing.png


Koson also allows the user to feed an Iterable as a source for an array, external JSON source embedding and custom types definition if you are not happy with the way Koson serializes all "primitive" types.


How does it work?

Long story short, everything you use is defined in a single Koson.kt file.


The obj is a function with a lambda with receiver parameter that operates over a Koson type. The Koson class is used to produce an ObjectType, that can add a finite instance type list to the object.


The arr is in fact a singleton (object in Kotlin) that uses the operator overload to permit the use of the [] as a function. It returns an ArrayType when invoked.


To sum it up, here is a diagram to show the full type hierarchy.


https://github.com/lectra-tech/koson/raw/master/image/koson-diagram.png


If you are curious and want to check out the implementation details, or why not contribute to improve this library, feel free to check the official github repository here : https://github.com/lectra-tech/koson


If you want to learn more about how Kotlin DSL can be written, you can watch this video (FR only) by Benoit introducing Kotlin DSLs.


There also is a video of a talk I gave (FR only) explaining how a private company can contribute to OSS and why they should, even if they think they don’t have material to do so. It also shortly explains how Koson’s publication process works.


Performance

Now that’s nice, but how does it behave? Is it faster than using your favorite serialization library?


To try answering that question, we’ve built 4 different benchmarks using the JMH OpenJDK tool. If you want to have a look or run the benchmarks on your machine, you’ll find all the details on our github page and in the following embedded project.


To sum it up, here are the results in operations/second (throughput mode), higher = better.


Benchmark

Score

Error

Units

BigObject - JSON-java

17120,661

± 45,741

ops/s

BigObject - Koson

17433,982

± 372,361

ops/s

BigObject (pretty) - JSON-java

8902,486

± 19,417

ops/s

BigObject (pretty) - Koson

10252,254

± 71,377

ops/s

BigArray - JSON-java

15272,946

± 139,435

ops/s

BigArray - Koson

14816,130

± 132,266

ops/s

BigArray (pretty) - JSON-java

7744,935

± 41,067

ops/s

BigArray (pretty) - Koson

8607,388

± 31,712

ops/s


Testing environment : 3.3 GHz Intel Core i5-6600, 4 cores, VM version: OpenJDK 11.0.1, 64-Bit Server VM, 11.0.1+13


Conclusion

If your only need is having to create JSON String representations from scratch in Kotlin, Koson is made for you. Feel free to ⭐ the project if you enjoy it, or why not suggest improvements using issues !


Thanks ;)