json5k

Build status API documentation

This is a JSON5 library for the kotlinx.serialization framework. Targeting the JVM, it serializes Kotlin object hierarchies into standard-compliant JSON5 output and vice versa.

Key features

  • Compliance with v1.0.0 of the JSON5 specification
  • Support for polymorphic types and configurable class discriminator names
  • Carefully composed error messages for deserialization errors
  • Support for the serialization of comments for class properties
  • Rejection of duplicate keys during deserialization

Unit tests for the most important application scenarios exist, but the framework has not been deployed to production yet. In addition, benchmarking and performance optimization are still to be done.

Bug reports and other feedback are highly welcome, for example via the issue tracker on GitHub.

Setup instructions

This repository contains a Gradle setup that compiles the library into a JAR file. Use this file according to your needs.

Recommended versions

json5k was tested against the following dependencies:

json5k Kotlin plugins Serialization runtime
v0.2.0 v1.7.20 v1.4.1
v0.1.0 v1.7.10 v1.4.0

Usage from Gradle

For evaluation purposes, the easiest solution might be to install the library to your local Maven repository:

./gradlew publishToMavenLocal

Afterwards, use it from build.gradle.kts as follows:

plugins {
    kotlin("jvm") version "1.7.20"
    kotlin("plugin.serialization") version "1.7.20"
}

repositories {
    mavenLocal {
        content {
            includeGroup("io.github.xn32")
        }
    }
}

dependencies {
    implementation("io.github.xn32:json5k:0.2.0")
}

However, keep the limitations of the local Maven repository in mind.

Usage examples

Non-hierarchical values

import io.github.xn32.json5k.Json5
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString

// Serialization:
Json5.encodeToString(5142) // 5142
Json5.encodeToString(listOf(4.5, 1.5e2, 1.2e15)) // [4.5,150.0,1.2E15]
Json5.encodeToString(mapOf("a" to 10, "b" to 20)) // {a:10,b:20}
Json5.encodeToString(Double.NEGATIVE_INFINITY) // -Infinity
Json5.encodeToString<Int?>(null) // null

// Deserialization:
Json5.decodeFromString<Int?>("113") // 113
Json5.decodeFromString<List<Double>>("[1.2, .4]") // [1.2, 0.4]
Json5.decodeFromString<Map<String, Int>>("{ a: 10, 'b': 20, }") // {a=10, b=20}
Json5.decodeFromString<Double>("+Infinity") // Infinity
Json5.decodeFromString<Int?>("null") // null

// Deserialization errors:
Json5.decodeFromString<Byte>("190")
    // UnexpectedValueError: signed integer in range [-128..127] expected at position 1:1
Json5.decodeFromString<List<Double>>("[ 1.0,,")
    // CharError: unexpected character ',' at position 1:7

Serializable classes

import io.github.xn32.json5k.Json5
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString

@Serializable
data class Person(val name: String, val age: UInt? = null)

// Serialization:
Json5.encodeToString(Person("John", 31u)) // {name:"John",age:31}
Json5.encodeToString(Person("Jane")) // {name:"Jane"}

// Deserialization:
Json5.decodeFromString<Person>("{ name: 'Carl' }") // Person(name=Carl, age=null)
Json5.decodeFromString<Person>("{ name: 'Carl', age: 42 }") // Person(name=Carl, age=42)

// Deserialization errors:
Json5.decodeFromString<Person>("{ name: 'Carl', age: 42, age: 10 }")
    // DuplicateKeyError: duplicate key 'age' specified at position 1:26

Classes with @SerialName annotations

import io.github.xn32.json5k.Json5
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString

@Serializable
data class IntWrapper(@SerialName("integer") val int: Int)

// Serialization:
Json5.encodeToString(IntWrapper(10)) // {integer:10}

// Deserialization:
Json5.decodeFromString<IntWrapper>("{ integer: 10 }") // IntWrapper(int=10)

// Deserialization errors:
Json5.decodeFromString<IntWrapper>("{ int: 10 }")
    // UnknownKeyError: unknown key 'int' specified at position 1:3

Polymorphic types

import io.github.xn32.json5k.ClassDiscriminator
import io.github.xn32.json5k.Json5
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString

@Serializable
@ClassDiscriminator("mode")
sealed interface Producer

@Serializable
@SerialName("numbers")
data class NumberProducer(val init: UInt) : Producer

// Serialization:
Json5.encodeToString<Producer>(NumberProducer(10u)) // {mode:"numbers",init:10}

// Deserialization:
Json5.decodeFromString<Producer>("{ init: 0, mode: 'numbers' }") // NumberProducer(init=0)

// Deserialization errors:
Json5.decodeFromString<Producer>("{ init: 0 }")
    // MissingFieldError: missing field 'mode' in object at position 1:1

Serialization of comments for class properties

import io.github.xn32.json5k.Json5
import io.github.xn32.json5k.SerialComment
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString

@Serializable
data class Person(
    val name: String,
    val age: UInt? = null
)

@Serializable
data class Event(
    @SerialComment("First day of the event")
    val date: String,
    @SerialComment("Registered attendees")
    val attendees: List<Person>
)

val json5 = Json5 {
    prettyPrint = true
}

println(
    json5.encodeToString(
        Event("2022-10-04", listOf(Person("Emma", 31u)))
    )
)

Running this code will produce the following output:

{
    // First day of the event
    date: "2022-10-04",
    // Registered attendees
    attendees: [
        {
            name: "Emma",
            age: 31
        }
    ]
}

Configuration options

Control generated JSON5 output as follows:

import io.github.xn32.json5k.Json5
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString

val json5 = Json5 {
    prettyPrint = true
    indentationWidth = 2
    useSingleQuotes = true
    quoteMemberNames = true
    encodeDefaults = true
}

@Serializable
data class Person(val name: String, val age: UInt? = null)

println(json5.encodeToString(Person("Oliver")))

This will result in the following output:

{
  'name': 'Oliver',
  'age': null
}

Further examples

See the unit tests for serialization and deserialization for more examples.

GitHub

View Github