# The fastest and safest JSON parser and serializer for Scala

# Introduction

[Jsoniter-Scala](https://github.com/plokhotnyuk/jsoniter-scala) is a zero-dependency, high-performance, actively maintained JSON parser and serializer library for the Scala programming language. The library offers many features. The Jsoniter-Scala design aims to provide fast and efficient parsing and serialization of JSON data. One of its primary goals is the automatic generation of safe JSON codecs supporting various [built-in data types](https://github.com/plokhotnyuk/jsoniter-scala#features-and-limitations). It also focuses on flexibility and customizable encoding/decoding options. As a result, it's a perfect choice for handling either small JSON messages or large JSON data sets. The library also provides a range of performance optimization techniques to process large JSON data sets efficiently. Those reasons make the Jsoniter-Scala popular for building high-performance web services and other data-intensive applications.

# Dependencies

The latest version of Jsoniter-Scala is available in the [Maven Central repository](https://mvnrepository.com/artifact/com.github.plokhotnyuk.jsoniter-scala).

```scala
// Use the %%% operator instead of %% for Scala.js and Scala Native 
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.19.0",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.19.0" % Provided
```

### Supported platforms

Released for 2.12.x, 2.13.x and 3.x. ([JVM](https://www.scala-lang.org/download/all.html)/[JS](https://www.scala-js.org/)/[Native](https://scala-native.org/en/stable/)).

The latest version requires JDK 11+ (use the library version 2.13.5.x for JDK 8+). Support for [GraalVM Native Image](https://www.graalvm.org/22.0/reference-manual/native-image/) 22+ without any platform restrictions.

Whatever your target is, a straightforward choice.

# Security

The Jsoniter-Scala design focuses on security, offering configurable limits for suboptimal data structures with safe defaults to be resilient for DoS attacks. In addition, it generates codecs that create instances of a fixed set of classes during parsing to avoid RCE attacks.

Unfortunately, this is not standard, and many competing libraries are vulnerable. It's the thing that everyone should be concerned about. I don't want to scare you, but you should watch the ["JSON Denial of Service"](https://www.youtube.com/watch?v=3Cz6D8JLSSA) video by Sam Halliday.

# Performance

Please find [the latest results of benchmarks](https://plokhotnyuk.github.io/jsoniter-scala/) (using various JVMs). Also, see how Jsoniter-Scala looks compared to popular competitors such as Jackson, Circe, uPickle, zio-json, and others.

Please see an example diagram for GeoJSON Reading, showing a real-world message sample, a JSON full of floating point numbers. The Jsoniter-Scala performs very well and is outstanding compared to others.

![Please see an example diagram for GeoJSON Reading, showing a real-world message sample, a JSON full of floating point numbers.](https://cdn.hashnode.com/res/hashnode/image/upload/v1670591215732/EIkwC-iDy.png align="center")

When browsing the [rich set of benchmarks](https://plokhotnyuk.github.io/jsoniter-scala/#details), see an interesting `gc.alloc.rate.norm`, which shows that the Jsoniter-Scala allocates much less than others. It's an option to select in the Score drop-down list at the top right corner of the benchmark page.  
(Please also find [a bit of the theory](https://ionutbalosin.com/2019/12/jvm-garbage-collectors-benchmarks-report-19-12/) about what those benchmarks are about).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1670592612624/wI3lf2UiR.png align="center")

[See also how well it performs in browsers](https://plokhotnyuk.github.io/jsoniter-scala/index-scalajs.html) and competes with other libraries that support Scala.js.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1670592950397/FKeYMQ5nn.png align="center")

Last but not least, see also charts on [running benchmarks on all CPUs simultaneously](https://plokhotnyuk.github.io/jsoniter-scala/index-t16.html).

You can see that JVM on a notebook used for benchmark execution can allocate up to 15Gb/s rate. However, you cannot scale by adding more threads after reaching this rate. For example, if some parser in one thread allocates at a 10Gb/s rate, it doesn't increase throughput after using more than two threads.

### Under the hood - why is it so fast?

While using a codec per class is a popular pattern for most JSON parsers, for the Jsoniter-Scala, it is an anti-pattern (with some rare exceptions). The Jsoniter-Scala provides full auto-derivation, so one needs to call derivation macros only for top-level types. It is handy and more efficient in most cases. Still, the Jsoniter-Scala can generate code that duplicates when messages have common parts that will provide pressure on the JIT compiler and CPU front-end in runtime. On the other hand, full semi-auto (codec per class) brings too many mega-morphic virtual method calls which cannot be in-lined. The most optimal way is somewhere in the middle for the arbitrary case when looking from the JVM side.

# Goals

*   **Safety**: safe defaults oriented for different types of attacks,
    
*   **Correctness**: RFC-8259; parsing numbers without losing precision; do not use placeholder characters,
    
*   **Performance**: ultra-fast parsing and serialization with minimum allocations and copying,
    
*   **Productivity**: a simple one-liner definition for nested data structure without the need to derive intermediate codecs,
    
*   **Ergonomics**: flexibility with safety-oriented defaults
    

See also the [more detailed description here](https://github.com/plokhotnyuk/jsoniter-scala#goals).

## How to use

Derive a codec for the top-level type to parse or serialize. We create a codec as below. Do it in the companion object for later auto-import of the codec in all places you'll use it. In Scala 2 we use `implicit val` instead of `given`.

```scala
import com.github.plokhotnyuk.jsoniter_scala.core.*
import com.github.plokhotnyuk.jsoniter_scala.macros.*

final case class Driver(name: String, car: Seq[Car])

object Driver:
  given codec: JsonValueCodec[Driver] = JsonCodecMaker.make

final case class Car(id: Long, brand: String, model: Option[String])
```

Now, we can parse and serialize it with ease.

```scala
// serialize object to JSON string
val driver: Driver     = ???
val serialized: String = writeToString(driver)

// deserialize JSON string to object
val json: String         = ???
val deserialized: Driver = readFromString[Driver](json)
```

# Practical suggestions and configuration

### Hint: a syntactic sugar

The way I use Jsoniter is `payload.fromJson` , and `object.toJson` .

The above example would look:

```scala
// serialize object to JSON string
val driver: Driver     = ???
val serialized: String = driver.toJson

// deserialize JSON string to object
val json: String         = ???
val deserialized: Driver = json.fromJson[Driver]
```

If you like those shorthand calls, enable them using the following code. The below example is for the `Try` from StdLib, but you might implement something similar for an IO monad of your choice. For example, using the below code, you might plug the `JsoniterSyntaticSugar` trait somewhere and enjoy `fromJson` and `toJson` syntactic sugar.

```scala
import com.github.plokhotnyuk.jsoniter_scala.core.*
import scala.util.Try

trait JsoniterSyntaticSugar:
  extension (payload: String)
    def fromJson[T](using JsonValueCodec[T]): Try[T] =
      Try(readFromString(payload))
  extension [T](obj: T)
    def toJson(using JsonValueCodec[T]): Try[String] =
      Try(writeToString(obj))
```

### Changing defaults for empty data

For most cases, you might use defaults (focused on performance), so define a codec for the top-level object, and that's it. Defaults are excellent, and I use them apart from one exceptional case regarding ADT that I will mention later in this chapter.

However, suppose one wants to modify the default behaviour in specific cases. E.g. the default absence of null data, omitting default values or empty collections.  
What does it mean?

### Transient None

Let's use the `Driver` example case class, and serialize the following:

```scala
val cars: Seq[Car] =
  Seq(
    Car(1L, "Aston Martin", None),
    Car(2L, "Kenworth", Some("W900"))
  )
val driver = Driver("James", cars)
val json = driver.toJson
```

Below is the result of how the `json` looks. Note that the first car doesn't have the "model" property (as this was marked `None`, so a `null` in JSON and omitted by default).

```json
{
  "name": "James",
  "car": [
    {
      "id": 1,
      "brand": "Aston Martin"
    },
    {
      "id": 2,
      "brand": "Kenworth",
      "model": "W900"
    }
  ]
}
```

We might use [codec configuration](https://github.com/plokhotnyuk/jsoniter-scala/blob/02169c801d9e21ca426115a8e71134de262ab02e/jsoniter-scala-macros/shared/src/main/scala-2/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala) if there is a good reason to preserve those. If you scroll up to the code snippet with the `Driver` case class, you see that the codec in the companion object is defined as `JsonCodecMaker.make` . We might define it as follows: `JsonCodecMaker.make(CodecMakerConfig.withTransientNone(false))` .

Now, the first car from the above JSON will have the explicit `null` information:

`{"id":1,"brand":"Aston Martin","model":null}`

### Transient Empty

Regarding the empty collections (`Nil`), if we serialize `Driver("James", Nil)` , the result is just only the `{"name":"James"}`.

Now, let's define our codec considering those two settings.

```scala
given codec: JsonValueCodec[Driver] = 
  JsonCodecMaker.make {
    CodecMakerConfig
        .withTransientNone(false)
        .withTransientEmpty(false)
  }
```

The `withTransientEmpty(false)` setting makes the `Driver("James", Nil)` turns into `{"name":"James","car":[]}`.

Also, if the earlier discussed `None` appears in the car's description, it will result in a `null` in JSON.

### Transient Default

Say, a default car in our fleet is Aston Martin. We redefine our case class as follows.

```scala
final case class Car(
  id: Int, 
  brand: String = "Aston Martin", 
  model: Option[String]
)
```

If it's our explicit business constraint, the other system (receiving information) knows that absence means default. We don't need to send apparent details. The Jsoniter will skip it by default.

To change it, use `.withTransientDefault(false)`.

### Other configuration parameters

See also the [other available settings](https://github.com/plokhotnyuk/jsoniter-scala/blob/9c26389b71f8744b4c0ec6500c38c8f5a12cc76f/jsoniter-scala-macros/shared/src/main/scala-2/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala#L25) in the Scaladoc documentation.

### Renaming properties

There are cases we might want to rename the field. For example, say the remote API uses an unclear acronym or shortened property name. Another example is a reserved word like "type".

```scala
// the Scala compiler allows us to escape it with backticks
final case class Issue(number: Int, `type`: String)

// optionally use the "named" annotation offered by the Jsoniter-Scala
final case class Issue(number: Int, @named("type") issueType: String)
```

Alternatively, we can use CodecMakerConfig, which is helpful if you want to change all field names [from camelCase to snake\_case](https://github.com/plokhotnyuk/jsoniter-scala/blob/9c26389b71f8744b4c0ec6500c38c8f5a12cc76f/jsoniter-scala-macros/shared/src/main/scala-2/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala#L276).

```scala
given codec: JsonValueCodec[FooBarDto] =   
  JsonCodecMaker.make {
    CodecMakerConfig.withFieldNameMapper(
      JsonCodecMaker.enforce_snake_case
    )
  }
```

### Parsing and serializing ADT (Algebraic Data Types)

As mentioned earlier, I use defaults in most cases. The only exception is ADT. It's about the standard discriminator field. This default happened historically because the first Jsoniter-Scala usages were for the replacement of Jackson-Module-Scala.

Usually, you define a codec only for the top-level type. However, if you want to modify behaviour for a particular type down in the hierarchy, it's possible. For example, I do this for ADT-based enums (with `sealed trait` or `sealed abstract class`), and Scala 3 enums.

Example Scala 3 enum:

```scala
enum HandlingMode:
  case Auto, Manual

object HandlingMode:
  given codec: JsonValueCodec[HandlingMode] =
    JsonCodecMaker.makeWithoutDiscriminator
```

We must remember to add the codec without discriminator to prevent using the default codec. If we forget, we will see something unwanted in many cases.

Let's use the above enum and serialize the following object.

```scala
Issue(1, HandlingMode.Manual).toJson
```

We get: `{"number":1,"type":{"type":"Manual"}}`.  
But when adding the "without discriminator" option, we get what we want:  
`{"number":1,"type":"Manual"}` .

It's the only specific setting one need to remember. Apart from that, we can always use defaults. Probably it's a good topic for a custom Wartremover linter rule that we might add to the `wartremover/wartremover-contrib` project. Let's see. ;-)

### Serializing a simple value (not a JSON object)

According to RFC-8259, a valid JSON is an object, array, number, string, or one of three literal names: `false`, `null`, and `true`.

If you think about how to add a codec to a simple value like a string or boolean, an answer is to use a [value class](https://docs.scala-lang.org/overviews/core/value-classes.html) (`AnyVal`) or [Opaque Types](https://docs.scala-lang.org/scala3/book/types-opaque-types.html). Jsoniter-Scala supports value classes and opaque type aliases as the top-level type and nested (using automatically generated codec).

Please see an example value class as a top-level type when JSON's expected output is a simple boolean value.

```scala
final case class Reward(value: Boolean) extends AnyVal

object Reward:
  given codec: JsonValueCodec[Reward] = JsonCodecMaker.make
```

Now, the `Reward(true).toJson` gives us simply `true` as a serialized JSON string. We can do it similarly with other simple values.

Not different is with the Scala 3 opaque type.

```scala
opaque type Year = Int

object Year:
  given codec: JsonValueCodec[Year] = JsonCodecMaker.make
  def apply(value: Int): Year = value
```

The result of `Year(2023).toJson` is a JSON of `2023`.

Here, we define a codec for a top-level type and serialize it directly. We don't need to define the codec explicitly if it's nested in a data structure.

# Usages of Jsoniter-Scala

To name a few, you'll find it adopted in [Sttp](https://sttp.softwaremill.com/en/latest/json.html?highlight=jsoniter#jsoniter-scala) and [Tapir](https://tapir.softwaremill.com/en/latest/endpoint/json.html#jsoniter-scala) by Sofwaremill and [smithy4s](https://github.com/disneystreaming/smithy4s) by Disney Streaming. There's also a great [Dijon](https://github.com/jvican/dijon) library built on top of Jsoniter (a dynamically typed Scala JSON library). It's a great addition to your tests if you work with JSON fixtures and want to modify test data for different scenarios quickly. Please expect a separate article about Dijon.

Worth noting is also that [zio-json recommends Jsoniter-Scala as a more performant alternative](https://github.com/zio/zio-json/blob/ad8d3d5d8ad4b7b7b0aa4ad9edd8cbfbe77f5de8/README.md#even-moar-performance) (if one can sacrifice ZIO integration and consider the library-agnostic option).

# Summary

Jsoniter-Scala is a lightweight, ergonomic, high-performance library for working with JSON data in Scala. It's security and correctness oriented by design. Moreover, the library is actively maintained and supports the latest JSON and Scala ecosystem standards. Finally, it offers a wide range of integration options—ideal for projects requiring efficient JSON data handling.
