Skip to content
This repository has been archived by the owner on Sep 29, 2024. It is now read-only.

xn32/json5k

Repository files navigation

json5k

Build status codecov Maven Central Maven Snapshots API documentation

This is an experimental JSON5 library for Kotlin/JVM and Kotlin/Native. It makes use of the kotlinx.serialization framework to serialize object hierarchies into standard-compliant JSON5 text and vice versa.

Key features

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

Setup instructions

json5k is available on Maven Central:

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

repositories {
    mavenCentral()
}

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

Official versions are published for the following targets:

  • Java Virtual Machine: jvm
  • x86-64 platforms: linuxX64, macosX64, iosX64, mingwX64
  • Apple Silicon platforms: macosArm64, iosArm64, iosSimulatorArm64

Snapshot versions of the main branch are available from here.

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' 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' 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.