Kotlin serialization is a great library for serialisation in Kotlin. It is mainly geared towards serialising from objects to strings and back, but on closer look it also contains a comprehensive Json library. Even after discovering the documentation, though, the use of this new library might be confusing.
๐ Serialisation to objects
Consider a data class:
@Serializable
data class Credentials(
val publicKey: String,
val privateKey: String,
)
The @Serializable annotation enables the encoding to string and back:
val credentials = Credentials("publicKey", "privateKey")
val stringValue = Json.encodeToString(credentials)
println(stringValue)
val credentialsDecoded = Json.decodeFromString<Credentials>(stringValue)
println(credentialsDecoded.publicKey)
/*
output:
{"publicKey":"publicKey","privateKey":"privateKey"}
Credentials(publicKey=publicKey, privateKey=privateKey)
*/
If there is no need for a data class, the json string can also be decoded straight into a JsonObject
val jsonObject = Json.decodeFromString<JsonObject>(stringValue)
With object serialisation, the the library shines with its ease of use. With JSON, however, the use becomes more ambiguous.
๐ Serialisation to JSON string, manually
When creating a web requests, a separate class for posting the data is not required. Then, the request body can be created with the JSON features part of the serialisation library. In there, comprehensive function set exists to handle most JSON encoding problems.
Creating our credentials string, for example, would look like:
val credentials = JsonObject(
mapOf(
"publicKey" to JsonPrimitive("publicKey"),
"privateKey" to JsonPrimitive("privateKey")
)
)
val array = JsonArray(listOf(credentials))
println(array.toString())
/*
output:
[{"publicKey":"publicKey","privateKey":"privateKey"}]
*/
There is also a DSL version of this construction, which might be preferred:
val credentials = buildJsonArray {
addJsonObject {
put("publicKey", "publickey")
put("privateKey", "privateKey")
}
}
println(credentials.toString())
/*
output:
[{"publicKey":"publickey","privateKey":"privateKey"}]
*/
How to create a JsonObject from a map<*, *>?
For a map with generic keys and values, an extension method can be defined that checks and converts the primitives to a JsonObject.
fun Map<*, *>.toJsonElement(): JsonElement {
val map: MutableMap = mutableMapOf()
this.forEach {
val key = it.key as? String ?: return@forEach
val value = it.value ?: return@forEach
when (value) {
// convert containers into corresponding Json containers
is Map<*, *> -> map[key] = (value).toJsonElement()
is List<*> -> map[key] = value.toJsonElement()
// convert the value to a JsonPrimitive
else -> map[key] = JsonPrimitive(value.toString())
}
}
return JsonObject(map)
}
How to create a JsonObject from a List<*>?
For converting from a list, and extension method can also be defined.
fun List<*>.toJsonElement(): JsonElement {
val list: MutableList = mutableListOf()
fun List<*>.toJsonElement(): JsonElement {
val list: MutableList = mutableListOf()
this.forEach {
val value = it as? Any ?: return@forEach
when (value) {
is Map<*, *> -> list.add((value).toJsonElement())
is List<*> -> list.add(value.toJsonElement())
else -> list.add(JsonPrimitive(value.toString()))
}
}
return JsonArray(list)
}
๐ Serialisation for YAML and other formats
Only JSON JSON, and some experimental formats, are supported out of the box. For others, like YAML, an external library that implements a custom formatter, can be used.
With this dependency, a YAML string can decoded into a @Serializable object as follows:
val yamlEncoded =
"""
publicKey: "publicKey"
privateKey: "privateKey"
""".trimIndent()
val credentials =
Yaml.default.decodeFromString(
Credentials.serializer(),
yamlEncoded
)
println(credentials)
/*
output:
Credentials(publicKey=publicKey, privateKey=privateKey)
*/
๐ธ Pretty printing a JSON string
If JSON input is without line breaks, it can be useful to make it more human readable. The JSON library can then be utilised with the prettyPrint property.
val format = Json { prettyPrint = true }
val input = """
{"publicKey":"publicKey","privateKey":"privateKey"}
""".trimIndent()
val jsonElement = format.decodeFromString<JsonElement>(input)
val bodyInPrettyPrint = format.encodeToString(jsonElement)
println(bodyInPrettyPrint)
/*
output:
{
"publicKey": "publicKey",
"privateKey": "privateKey"
}
*/
As shown here, the input string needs to be decoded into a JsonElement, and then encoded back to string again. Only then, the prettyPrint property will cooperate in achieving our goal.
โ๏ธ Conclusion
Kotlinx.serialization is a great tool for serialising objects and parsing JSON strings. Since it is a new library within a new language, all of the features might not be obvious at first. Therefore, analysis of the documentation is encouraged before using it in code.
Sample code is available in tonisives repo.
Related tweet:
Using kotlinx.serialization to create JSON with DSLhttps://t.co/twLFmTpCfG
โ Tรตnis Tiganik (@tonisives) January 5, 2021