
Minimalistic JSON library offering robust performance, type-specific getters, and extensible classes without reflection or schema. Supports mutable and immutable containers, with Ktor content negotiation integration.
Essential-kson is a JSON library which:
The main differences with kotlinx-serialization-json are the extensibility and the absence of wrapping of raw values, along with nice type-specific getters.
Here is the list of currently supported targets:
The com.republicate.kson.Json interface is implemented by its two inner classes Json.Array and Json.Object, which are in turn inherited with Json.MutableArray andJson.MutableObject.
Json.Array extends List<Any?> and Json.Object extends Map<String, Any?>.
Objects compare as maps, regardless of key order, and numbers compare by value whatever their class (1, 1L and 1.0 are equal, 0.1f equals 0.1), so written and parsed values compare equal.
Using Maven:
<dependency>
<groupId>com.republicate.kson</groupId>
<artifactId>essential-kson</artifactId>
<version>2.15</version>
</dependency>
Using Gradle:
implementation 'com.republicate.kson:essential-kson:2.15'
The generic Json.parse(string_or_stream) method will return a com.republicate.kson.Json value containing a Json.Object or Json.Array object (or throw a JsonException if parsing failed).
If you want to parse a content without knowing if it's a JSON container or a simple JSON value,
you will call the Json.parseValue(string_or_stream) method to get an Any?.
Both methods accept an optional allowBigNumbers parameter (default: true). When true, numbers that don't fit in Long or Double are parsed as BigInteger/BigDecimal. When false, all numbers are parsed as Long or Double.
import com.republicate.kson.Json;
...
val container = Json.parse(string_or_stream);
// container will be a JSON object or a JSON array
if (container.isObject())
{
Json.Object obj = container.asObject();
...
}
val value = Json.parseValue(string_or_reader);
// value will either be a JSON container or a single Serializable value (or null)
// parse without big number support
val simple = Json.parse(jsonString, allowBigNumbers = false)
Containers toString() and toString(stream) methods will render JSON strings with proper quoting and encoding.
import com.republicate.kson.Json;
...
// getting a String
val json = container.toString();
// rendering towards a stream
container.toString(stream);
toPrettyString() methods will pretty-print the output.
The reentrant method Json.toJson(Any) will try hard to convert any standard container to a JSON structure.
The following specialized methods handle specific cases:
List<*>.toJsonArray()List<*>.toMutableJsonArray()Map<*,*>.toJsonObject()Map<*,*>.toMutableJsonObject()Iterable<Pair<*,*>>.toMJsonObject()Iterable<Pair<*,*>>.toMutableJsonObject()The library provides a lightweight DSL for building JSON structures inline:
import com.republicate.kson.obj
import com.republicate.kson.arr
// Objects: "key" to value
val user = obj {
"name" to "John"
"age" to 30
"active" to true
"address" to obj {
"city" to "Paris"
"zip" to "75001"
}
}
// Arrays: bracket syntax
val numbers = arr[1, 2, 3, 4]
// Mixed structures
val data = obj {
"items" to arr[1, 2, 3]
"matrix" to arr[arr[1, 2], arr[3, 4]]
"users" to arr[obj { "name" to "Alice" }, obj { "name" to "Bob" }]
}Caveat: The to operator inside obj { } both assigns to the object and returns a Pair. When using mapOf("k" to "v") inside the scope, "k" will be added to the enclosing object as a side effect. Use Pair("k", "v") or build maps outside the DSL scope to avoid this.
Use the following code to use essential-kson with ktor 1.x content negotiation:
import com.republicate.kson.Json
object KsonConverter: ContentConverter {
override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
val input = object: Json.Input {
val reader = content.toInputStream().reader(charset)
override fun read() = reader.read().toChar()
}
return Json.parseValue(input)
}
override suspend fun serialize(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any
): OutgoingContent? = TextContent(value.toString(), contentType.withCharsetIfNeeded(charset))
}
// and in ktor configuration section:
install(ContentNegotiation) {
register(ContentType.Application.Json, KsonConverter)
}
Use the following code to use essential-kson with ktor 2.x content negotiation:
import com.republicate.kson.Json
object KsonConverter: ContentConverter {
override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
val buffer = StringBuilder()
var first = true
while (true) {
if (first) first = false
else buffer.append('\n')
if (!content.readUTF8LineTo(buffer)) break
}
return Json.parseValue(buffer.toString())
}
override suspend fun serializeNullable(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any?
): OutgoingContent? {
if (value !is Json) throw IOException("content is not Json")
return TextContent(value.toString(), ContentType.Application.Json)
}
}
fun ContentNegotiationConfig.kson() {
register(
contentType = ContentType.Application.Json,
converter = KsonConverter)
}
// and in ktor configuration section:
install(ContentNegotiation) {
kson()
}
Use the following code to use essential-kson with ktor 2.x content negotiation:
import com.republicate.kson.Json
object KsonConverter: ContentConverter {
override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
val buffer = StringBuilder()
var first = true
while (true) {
if (first) first = false
else buffer.append('\n')
if (!content.readUTF8LineTo(buffer)) break
}
return Json.parseValue(buffer.toString())
}
override suspend fun serialize(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any?
): OutgoingContent? {
if (value !is Json) throw kotlinx.io.IOException("content is not Json")
return TextContent(value.toString(), ContentType.Application.Json)
}
}
fun ContentNegotiationConfig.kson() {
register(
contentType = ContentType.Application.Json,
converter = KsonConverter)
}
// and in ktor configuration section:
install(ContentNegotiation) {
kson()
}
Essential-kson is a JSON library which:
The main differences with kotlinx-serialization-json are the extensibility and the absence of wrapping of raw values, along with nice type-specific getters.
Here is the list of currently supported targets:
The com.republicate.kson.Json interface is implemented by its two inner classes Json.Array and Json.Object, which are in turn inherited with Json.MutableArray andJson.MutableObject.
Json.Array extends List<Any?> and Json.Object extends Map<String, Any?>.
Objects compare as maps, regardless of key order, and numbers compare by value whatever their class (1, 1L and 1.0 are equal, 0.1f equals 0.1), so written and parsed values compare equal.
Using Maven:
<dependency>
<groupId>com.republicate.kson</groupId>
<artifactId>essential-kson</artifactId>
<version>2.15</version>
</dependency>
Using Gradle:
implementation 'com.republicate.kson:essential-kson:2.15'
The generic Json.parse(string_or_stream) method will return a com.republicate.kson.Json value containing a Json.Object or Json.Array object (or throw a JsonException if parsing failed).
If you want to parse a content without knowing if it's a JSON container or a simple JSON value,
you will call the Json.parseValue(string_or_stream) method to get an Any?.
Both methods accept an optional allowBigNumbers parameter (default: true). When true, numbers that don't fit in Long or Double are parsed as BigInteger/BigDecimal. When false, all numbers are parsed as Long or Double.
import com.republicate.kson.Json;
...
val container = Json.parse(string_or_stream);
// container will be a JSON object or a JSON array
if (container.isObject())
{
Json.Object obj = container.asObject();
...
}
val value = Json.parseValue(string_or_reader);
// value will either be a JSON container or a single Serializable value (or null)
// parse without big number support
val simple = Json.parse(jsonString, allowBigNumbers = false)
Containers toString() and toString(stream) methods will render JSON strings with proper quoting and encoding.
import com.republicate.kson.Json;
...
// getting a String
val json = container.toString();
// rendering towards a stream
container.toString(stream);
toPrettyString() methods will pretty-print the output.
The reentrant method Json.toJson(Any) will try hard to convert any standard container to a JSON structure.
The following specialized methods handle specific cases:
List<*>.toJsonArray()List<*>.toMutableJsonArray()Map<*,*>.toJsonObject()Map<*,*>.toMutableJsonObject()Iterable<Pair<*,*>>.toMJsonObject()Iterable<Pair<*,*>>.toMutableJsonObject()The library provides a lightweight DSL for building JSON structures inline:
import com.republicate.kson.obj
import com.republicate.kson.arr
// Objects: "key" to value
val user = obj {
"name" to "John"
"age" to 30
"active" to true
"address" to obj {
"city" to "Paris"
"zip" to "75001"
}
}
// Arrays: bracket syntax
val numbers = arr[1, 2, 3, 4]
// Mixed structures
val data = obj {
"items" to arr[1, 2, 3]
"matrix" to arr[arr[1, 2], arr[3, 4]]
"users" to arr[obj { "name" to "Alice" }, obj { "name" to "Bob" }]
}Caveat: The to operator inside obj { } both assigns to the object and returns a Pair. When using mapOf("k" to "v") inside the scope, "k" will be added to the enclosing object as a side effect. Use Pair("k", "v") or build maps outside the DSL scope to avoid this.
Use the following code to use essential-kson with ktor 1.x content negotiation:
import com.republicate.kson.Json
object KsonConverter: ContentConverter {
override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
val input = object: Json.Input {
val reader = content.toInputStream().reader(charset)
override fun read() = reader.read().toChar()
}
return Json.parseValue(input)
}
override suspend fun serialize(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any
): OutgoingContent? = TextContent(value.toString(), contentType.withCharsetIfNeeded(charset))
}
// and in ktor configuration section:
install(ContentNegotiation) {
register(ContentType.Application.Json, KsonConverter)
}
Use the following code to use essential-kson with ktor 2.x content negotiation:
import com.republicate.kson.Json
object KsonConverter: ContentConverter {
override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
val buffer = StringBuilder()
var first = true
while (true) {
if (first) first = false
else buffer.append('\n')
if (!content.readUTF8LineTo(buffer)) break
}
return Json.parseValue(buffer.toString())
}
override suspend fun serializeNullable(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any?
): OutgoingContent? {
if (value !is Json) throw IOException("content is not Json")
return TextContent(value.toString(), ContentType.Application.Json)
}
}
fun ContentNegotiationConfig.kson() {
register(
contentType = ContentType.Application.Json,
converter = KsonConverter)
}
// and in ktor configuration section:
install(ContentNegotiation) {
kson()
}
Use the following code to use essential-kson with ktor 2.x content negotiation:
import com.republicate.kson.Json
object KsonConverter: ContentConverter {
override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
val buffer = StringBuilder()
var first = true
while (true) {
if (first) first = false
else buffer.append('\n')
if (!content.readUTF8LineTo(buffer)) break
}
return Json.parseValue(buffer.toString())
}
override suspend fun serialize(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any?
): OutgoingContent? {
if (value !is Json) throw kotlinx.io.IOException("content is not Json")
return TextContent(value.toString(), ContentType.Application.Json)
}
}
fun ContentNegotiationConfig.kson() {
register(
contentType = ContentType.Application.Json,
converter = KsonConverter)
}
// and in ktor configuration section:
install(ContentNegotiation) {
kson()
}