zero-json

High-performance JSON serializer/deserializer with zero extra allocations, zero-copy buffer wrapping, map/object inlining, polymorphic value-subclass support, and advanced parser hooks.

JVMKotlin/NativeWasmJS
GitHub stars2
Authorsadokky
Dependents0
OSS Health
LicenseApache License 2.0
Creation date9 months ago

Last activity6 months ago
Latest release0.5.2 (6 months ago)

Maven Central Version javadoc GitHub license

zero-json

Fast and powerful implementation of JSON format for kotlinx-serialization.

  • Compatibility: Can be used as a drop-in replacement for kotlinx-serialization-json.
  • Map and object inlining: Mark a class property with @JsonInline to inline its serialized form. Only final classes and Map instances can be inlined.
  • Zero extra allocation: Only deserialized objects are allocated. Exceptions include kotlinx serializers (that use ChunkedDecoder) and Float/Double types (still allocates much less than most serializers). On Kotlin/JS, it may allocate much more — not much we can do about it.
  • Zero-copy: Deserialize objects without intermediate copies by wrapping any byte buffer. Buffer wrapping is done through a simple Buffer interface that requires only size property and get method to be implemented.
  • Advanced deserializers (experimental): custom serializers has access to underlying parser (JsonReader). That allows implementing simple and efficient content-based polymorphism without extra allocations of JsonElement.

Other differences from kotlinx-serialization-json

Extra features

  • Option to serialize structured map keys as escaped strings.
  • Out-of-the-box support for polymorphic value subclasses, automatically serializing them with a type and value field:
@Serializable sealed interface Base
@Serializable value class Foo(val int: Int): Base
val s = ZeroJson.encodeToString<Base>(Foo(42))
println(s)
// { "type": "Foo", "value": 42 }
println(ZeroJson.decodeFromString<Base>(s))
// 42

Limitations

  • The input JSON string must be fully loaded into memory before decoding. This library is not suitable for deserializing large JSON files. This limitation is irrelevant for typical REST APIs, where request and response sizes are limited
  • No array polymorphism
  • External serializers and partial custom serializers are not supported because of the bug.
  • Duplicate JSON object keys are not allowed
  • On JS: no dynamic support
  • On JVM: Serialization using InputStream/OutputStream is not recommended. Nearly all I/O libraries and frameworks provide access to underlying array or buffer abstractions. Wrapping these is the intended way to use this library. For any streaming workload, we recommend using the original kotlinx-serialization-json instead.
  • No prettyPrint option

Setup

Setup Google repository (zero-json uses androidx.collection:collection under the hood):

repositories {
    google()
}

Standalone (zero-json-core)

This option allows you to use all the features specific to zero-json.

implementation("io.github.adokky:zero-json-core:0.5.2")

Drop-in replacement (zero-json-kotlinx)

Compatible with kotlinx-serialization-json 1.9.0.

Not available for JavaScript, though WasmJs is supported.

implementation("io.github.adokky:zero-json-kotlinx:0.5.2")

Exclude the original kotlinx-serialization-json:

configurations.configureEach {
    resolutionStrategy {
        exclude("org.jetbrains.kotlinx", "kotlinx-serialization-json")
    }
}

Both zero-json-core and zero-json-kotlinx can be used simultaneously.

@JsonInline

  • Applicable only for serializable elements of kinds StructureKind.MAP and StructureKind.CLASS
  • Nested inline properties are supported

Quick example:

@Serializable
class Person(
    val name: String,
    val age: Int, 
    @JsonInline val location: Location,
    @JsonInline val extra: Map<String, String>?,
)

@Serializable
class Location(@JsonInline val country: Country, val city: String)

@Serializable
class Country(
    @JsonNames("countryName") val name: String,
    @JsonNames("countryCode") val code: Int
)

ZeroJson.encodeToString(
    Person(
        name = "Alex",
        age = 44,
        location = Location(
            country = Country("Dreamland", 1234),
            city = "SimCity"
        ),
        extra = mapOf("avatar" to "https://cdn.example/profile_picture23535")
    )
)

Result:

{
    "name": "Alex",
    "age": 44,
    "countryName": "Dreamland",
    "countryCode": 1234,
    "city": "SimCity",
    "avatar": "https://cdn.example/profile_picture23535"
}

Performance

zero-json is built for fast UTF-8 encoding/decoding — super common in network-related workloads. It's tuned for JVM backend services which handle tons of client requests, so serialization is one of the main bottlenecks. Client apps rarely hit serialization limits — they're more bogged down by UI or network delays.

Benchmarks use JMH, so results may vary on other platforms.

Decoding (smaller is better):

bytes_kotlinx            136.218 ±  1.453  us/op
bytes_zjson              107.799 ±  0.891  us/op
string_kotlinx            80.194 ±  0.751  us/op
string_zjson             100.707 ±  0.770  us/op
tree_kotlinx              42.197 ±  3.291  us/op
tree_zjson                28.922 ±  0.574  us/op

Encoding (smaller is better):

bytes_kotlinx             36.517 ±  0.587  us/op
bytes_zjson               29.325 ±  0.553  us/op
string_kotlinx            27.550 ±  0.635  us/op
string_zjson              42.241 ±  4.009  us/op
tree_kotlinx              31.760 ±  3.394  us/op
tree_zjson                19.541 ±  0.352  us/op
  • bytes:
    • kotlinx: decodeFromStream, encodeToStream (ByteArrayOutputStream/ByteArrayInputStream)
    • zero-json: decodeFromBuffer, encodeToBuffer (ArrayBuffer)
  • string - decodeFromString, encodeToString
  • tree - decodeFromJsonElement, encodeToJsonElement
JVMKotlin/NativeWasmJS
GitHub stars2
Authorsadokky
Dependents0
OSS Health
LicenseApache License 2.0
Creation date9 months ago

Last activity6 months ago
Latest release0.5.2 (6 months ago)

Maven Central Version javadoc GitHub license

zero-json

Fast and powerful implementation of JSON format for kotlinx-serialization.

  • Compatibility: Can be used as a drop-in replacement for kotlinx-serialization-json.
  • Map and object inlining: Mark a class property with @JsonInline to inline its serialized form. Only final classes and Map instances can be inlined.
  • Zero extra allocation: Only deserialized objects are allocated. Exceptions include kotlinx serializers (that use ChunkedDecoder) and Float/Double types (still allocates much less than most serializers). On Kotlin/JS, it may allocate much more — not much we can do about it.
  • Zero-copy: Deserialize objects without intermediate copies by wrapping any byte buffer. Buffer wrapping is done through a simple Buffer interface that requires only size property and get method to be implemented.
  • Advanced deserializers (experimental): custom serializers has access to underlying parser (JsonReader). That allows implementing simple and efficient content-based polymorphism without extra allocations of JsonElement.

Other differences from kotlinx-serialization-json

Extra features

  • Option to serialize structured map keys as escaped strings.
  • Out-of-the-box support for polymorphic value subclasses, automatically serializing them with a type and value field:
@Serializable sealed interface Base
@Serializable value class Foo(val int: Int): Base
val s = ZeroJson.encodeToString<Base>(Foo(42))
println(s)
// { "type": "Foo", "value": 42 }
println(ZeroJson.decodeFromString<Base>(s))
// 42

Limitations

  • The input JSON string must be fully loaded into memory before decoding. This library is not suitable for deserializing large JSON files. This limitation is irrelevant for typical REST APIs, where request and response sizes are limited
  • No array polymorphism
  • External serializers and partial custom serializers are not supported because of the bug.
  • Duplicate JSON object keys are not allowed
  • On JS: no dynamic support
  • On JVM: Serialization using InputStream/OutputStream is not recommended. Nearly all I/O libraries and frameworks provide access to underlying array or buffer abstractions. Wrapping these is the intended way to use this library. For any streaming workload, we recommend using the original kotlinx-serialization-json instead.
  • No prettyPrint option

Setup

Setup Google repository (zero-json uses androidx.collection:collection under the hood):

repositories {
    google()
}

Standalone (zero-json-core)

This option allows you to use all the features specific to zero-json.

implementation("io.github.adokky:zero-json-core:0.5.2")

Drop-in replacement (zero-json-kotlinx)

Compatible with kotlinx-serialization-json 1.9.0.

Not available for JavaScript, though WasmJs is supported.

implementation("io.github.adokky:zero-json-kotlinx:0.5.2")

Exclude the original kotlinx-serialization-json:

configurations.configureEach {
    resolutionStrategy {
        exclude("org.jetbrains.kotlinx", "kotlinx-serialization-json")
    }
}

Both zero-json-core and zero-json-kotlinx can be used simultaneously.

@JsonInline

  • Applicable only for serializable elements of kinds StructureKind.MAP and StructureKind.CLASS
  • Nested inline properties are supported

Quick example:

@Serializable
class Person(
    val name: String,
    val age: Int, 
    @JsonInline val location: Location,
    @JsonInline val extra: Map<String, String>?,
)

@Serializable
class Location(@JsonInline val country: Country, val city: String)

@Serializable
class Country(
    @JsonNames("countryName") val name: String,
    @JsonNames("countryCode") val code: Int
)

ZeroJson.encodeToString(
    Person(
        name = "Alex",
        age = 44,
        location = Location(
            country = Country("Dreamland", 1234),
            city = "SimCity"
        ),
        extra = mapOf("avatar" to "https://cdn.example/profile_picture23535")
    )
)

Result:

{
    "name": "Alex",
    "age": 44,
    "countryName": "Dreamland",
    "countryCode": 1234,
    "city": "SimCity",
    "avatar": "https://cdn.example/profile_picture23535"
}

Performance

zero-json is built for fast UTF-8 encoding/decoding — super common in network-related workloads. It's tuned for JVM backend services which handle tons of client requests, so serialization is one of the main bottlenecks. Client apps rarely hit serialization limits — they're more bogged down by UI or network delays.

Benchmarks use JMH, so results may vary on other platforms.

Decoding (smaller is better):

bytes_kotlinx            136.218 ±  1.453  us/op
bytes_zjson              107.799 ±  0.891  us/op
string_kotlinx            80.194 ±  0.751  us/op
string_zjson             100.707 ±  0.770  us/op
tree_kotlinx              42.197 ±  3.291  us/op
tree_zjson                28.922 ±  0.574  us/op

Encoding (smaller is better):

bytes_kotlinx             36.517 ±  0.587  us/op
bytes_zjson               29.325 ±  0.553  us/op
string_kotlinx            27.550 ±  0.635  us/op
string_zjson              42.241 ±  4.009  us/op
tree_kotlinx              31.760 ±  3.394  us/op
tree_zjson                19.541 ±  0.352  us/op
  • bytes:
    • kotlinx: decodeFromStream, encodeToStream (ByteArrayOutputStream/ByteArrayInputStream)
    • zero-json: decodeFromBuffer, encodeToBuffer (ArrayBuffer)
  • string - decodeFromString, encodeToString
  • tree - decodeFromJsonElement, encodeToJsonElement