Kotlin Json Performance libraries

by coding flower

Json libraries for server-side development

Jackson and Gson are the two most popular JSON libraries for Java, so in most cases, these two dependencies are used with kotlin on the backend. 8 October 2020 that day quite important in the case of JSON processing in Kotlin world, because kotlinx.serialization was released. I was curious about how the JetBrains library's performance looks like compares to the two most popular Java ones. That's why I decided to do some benchmark tests.

Benchmark details

JMH

Java Microbenchmark Harness allows to runs nano/micro/milli/macro benchmarks written in languages targetting the JVM.

Test parameters

3 different libraries:

  • Jackson
  • Gson
  • Kotlinx

4 different jsons:

  • simple json
  • list with 1000 simple jsons
  • complex json
  • list with 100 complex jsons

2 behaviors to benchmark:

  • serialization
  • deserialization

This gives me 24 fours tests, for all tests I measured the throughput.

Throughput: operations per unit of time.
Runs by continuously calling Benchmark methods, counting the total throughput over all worker threads. This mode is time-based, and it will run until the iteration time expires.

In these tests, all results are operations/milliseconds.

Simple json example

{
  "website": "www.codingflower.com"
}

Complex json example

{
  "id": "5fd2c8d36ce07686bcc76b2b",
  "index": 0,
  "guid": "25bbb5bd-4fcb-4dc5-ba93-f83816b4e502",
  "isActive": true,
  "picture": "http://placehold.it/32x32",
  "age": 25,
  "eyeColor": "blue",
  "name": {
    "first": "Shawna",
    "last": "Byers"
  },
  "company": "ZILLA",
  "tags": [
    "sit",
    "cillum",
    "irure",
    "deserunt",
    "elit",
    "mollit",
    "ad"
  ],
  "range": [
    0,
    1,
    8,
    9
  ],
  "friends": [
    {
      "id": 0,
      "name": "Hopkins Savage"
    },
    {
      "id": 1,
      "name": "Tasha Cooley"
    },
    {
      "id": 2,
      "name": "Tami Newman"
    },
    {
      "id": 3,
      "name": "Angelica Hopper"
    },
    {
      "id": 4,
      "name": "Valdez Terrell"
    },
    {
      "id": 5,
      "name": "Carlson Ramos"
    },
    {
      "id": 6,
      "name": "Elinor Pope"
    },
    {
      "id": 7,
      "name": "Vincent Rush"
    },
    {
      "id": 13,
      "name": "Elisa Burt"
    },
    {
      "id": 14,
      "name": "Maribel Jacobson"
    },
    {
      "id": 15,
      "name": "Blanche Fleming"
    },
    {
      "id": 16,
      "name": "Daisy Robles"
    },
    {
      "id": 17,
      "name": "Schultz Booker"
    }
  ],
  "rating": {
    "1": " Mcclain",
    "2": " Villarreal",
    "3": " Bailey",
    "4": " Keith",
    "5": " Michael",
    "6": " Booth",
    "14": " Joseph",
    "15": " Erickson",
    "16": " Willis",
    "17": " Carney",
    "18": " Hansen",
    "19": " Spencer",
    "20": " Le"
  }
}

Benchmark test cases

Simple JSON serialization

All three libraries has to serialize simple object to json. The object looks like this:

SimpleJson("www.codingflower.com")

I serialize this object with three libriaries:

@Benchmark
    fun jackson(bl: Blackhole) {
        val text = jackson.writeValueAsString(simpleJson)
        bl.consume(text)
    }

    @Benchmark
    fun gson(bl: Blackhole) {
        val text = gson.toJson(simpleJson)
        bl.consume(text)
    }

    @Benchmark
    fun kotlin(bl: Blackhole) {
        val text = kotlinx.encodeToString(simpleJson)
        bl.consume(text)
    }

The results are presented on the chart below:

Simple JSON deserialization

This time deserialization of simple JSON was measured:

@Benchmark
    fun jackson(bl: Blackhole) {
        val simpleJson = jackson.readValue(simpleText, SimpleJson::class.java)
        bl.consume(simpleJson)
    }

    @Benchmark
    fun gson(bl: Blackhole) {
        val simpleJson = gson.fromJson(simpleText, SimpleJson::class.java)
        bl.consume(simpleJson)
    }

    @Benchmark
    fun kotlinx(bl: Blackhole) {
        val simpleJson = kotlinx.decodeFromString<SimpleJson>(simpleText)
        bl.consume(simpleJson)
    }

The chart with result looks like this:

Simple objects list serialization

One thousand of SimpleJson object were serialized to JSON string. I am not pasting the code here, because the benchmark code is the same as in Simple JSON Serialization paragraph, with one obvious difference, instead of serialization of one object, libraries serialize 1000 objects.

Simple JSON list deserialization

The chart with the result of benchmarking 1000 simpleJson in one JSON array is under the code snippet:

@Benchmark
    fun jackson(bl: Blackhole) {
        val simpleJsons = jackson.readValue<List<SimpleJson>>(text)
        bl.consume(simpleJsons)
    }

    @Benchmark
    fun gson(bl: Blackhole) {
        val itemType = object : TypeToken<List<SimpleJson>>() {}.type
        val simpleJsons = gson.fromJson<List<SimpleJson>>(text, itemType)
        bl.consume(simpleJsons)
    }

    @Benchmark
    fun kotlinx(bl: Blackhole) {
        val simpleJsons = kotlinx.decodeFromString<List<SimpleJson>>(text)
        bl.consume(simpleJsons)
    }

Summary of simple JSON processing

  • Jackson is the fastest when it comes to serialization, but is almost the slowest when it comes to deserialization.
  • Gson is the fastest in deserialization operations, but it's also really slow during list serialization.
  • Kotlinx has a similar performance as Jackson with deserialization, but it's much faster than Jackson when the task is to deserialize the given list. Kotlinx deals with list serialization better than Gson, but is still much slower than Jackson. it is the slowest of all three during simple serialization comparison.

Complex JSON serialization

The same code like in Simple JSON Serialization paragraph, but much more complex object was serialized.

complexJson = ComplexJson(
            id = "ID",
            index = 0,
            guid = "guid",
            isActive = true,
            picture = "picture",
            age = 20,
            eyeColor = "Blue",
            name = Name("John", "Dee"),
            company = "company",
            tags = listOf("Kotlin", "JMH", "Performance", "Json"),
            range = listOf(2, 3, 4),
            friends = listOf(
                Friend(1, "John"),
                Friend(2, "Stefan"),
                Friend(3, "Garry"),
                Friend(4, "Tom"),
                Friend(5, "Kimi")
            ),
            rating = mapOf(
                1 to "One",
                2 to "Two",
                3 to "Three",
                4 to "Four",
                5 to "Five",
            )
        )

The result are presented on this chart:

Complex JSON deserialization

Here are the results of deserialization of complex json:

Complex objects list serialization

The results of 100 objects serialized by Jackson, Gson and Kotlinx:

Complex JSON list deserialization

This chart presents the results of benchmarking quite big JSON file which included 100 complexJson:

Summary of complex JSON processing

  • Jackson is the fastest in serialization, but it is also the slowest in deserialization.
  • Gson copes best with deserialization, but it takes much more time to serialize complex objects with it.
  • Kotlinx is almost in the middle between the results of the most operations per millisecond and the lowest number of operations per millisecond.

Conclusions

The results are surprising to me, I expected Gson and Jackson to have similar results for serialization and deserialization.

Based on the benchmark data we can easily see that Jackson specializes in serialization and Gson in deserialization.

Kotlinx positions itself in an interesting place, it is the most universal. The differences between it and the fastest are much smaller than the differences between the fastest and the slowest.

Of course, it is possible to use both libraries Jackson to perform serialization and Gson to do deserialization, but I think it will only matter in projects where you have to care about tenths of a millisecond.

If have to choose a library for a small service or my side project I would give it a try to kotlinx because is written in kotlin and has better support for language-specific features like default values. In cases where it foresees the use of less popular library functionalities, I will choose Jackson because I want to avoid childhood diseases in kotlinx, as version 1 has recently appeared.

Related Posts

Leave a Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More