Ported from Medium, original source here
Kotlin has recently released a library for serialization. To put things in perspective, before this there was no serialization lib provided by kotlin but you could use any platform supported serialization libraries with Kotlin classes(eg. Gson for JVM/Android)
But that’s where Kotlin’s serialization library is different, it’s cross platform and supports multiple formats. The whole lib is built on top of Kotlin’s multiplatform concept.
Currently kotlinx.serialization supports three formats
- JSON
- CBOR
- Protobuf
And platforms are JVM and JS, but others should get added soon as Kotlin/native becomes mature.
Let’s take a look at JSON
Marking @Serializable
will make a class serializable.
@Serializable
data class Destination(
var name : String? = "",
var country : String? = "",
var code : Int = 0,
)
stringify will convert a serializable object to JSON string.
val delhi = Destination(name = "Delhi", country = "India", code = 0)
val delhiAsString = JSON.stringify(delhi)
println(delhiAsString)
// {"name":"Delhi","country":"India","code":0}
parse will convert well formed json back to a data class.
val newYork = JSON.parse<Destination>(
"{\"name\":\"New York\",\"country\":\"USA\",\"code\":3}"
)
println(newYork)
// Destination(name=New York, country=USA, code=3)
Parsing has multiple options like
val paris = JSON.unquoted.parse<Destination>(
"{name:Paris,country:France,code:10}")
println(paris)
// Destination(name=Paris, country=France, code=10)
In above example, json string doesn’t have quotes. We can parse such json with JSON.unquoted
. This can be helpful during debugging.
Here’s a definition of all such properties available
Additional Properties
Often our data objects have properties which could be optional. For example, there could be extra properties which we initialize on client side which server may not provide while fetching the json. Similarly there may be properties which may or may not be present in json. Fortunately the lib handles both cases.
@Optional
var isMetro : Boolean = false,
@Transient
var favorite : Boolean = false
Optional — This value will considered during serialization/deserialization. but if not present, it won’t break serialization.
Transient — This value will not be considered during serialization/deserialization. But if present in json, it will cause an exception.
var barcelona = JSON.unquoted.parse<Destination>(
"{name:Barcelona,country:Spain,code:5}")
println(barcelona)
// Only optional and transient missing, so serialization works
// Destination(name=Paris, country=France, code=10, isMetro=false, favorite=false)
barcelona = JSON.unquoted.parse(
"{name:Barcelona,country:Spain,code:5,isMetro:true}")
println(barcelona)
// Destination(name=Barcelona, country=Spain, code=5, isMetro=true, favorite=false)
// Optional property isMetro is updated but Transient property favorite remains same
barcelona = JSON.unquoted.parse(
"{name:Barcelona,code:5,isMetro=true}")
// This will break as "country" is a required field.
barcelona = JSON.unquoted.parse(
"{name:Barcelona,country:Spaincode:5,
isMetro=true,favorite=true}")
// This will break as "favorite" is not recognised by serializer
If you’re not sure about consistency of values in json, using JSON.nonstrict might be a good idea to avoid exceptions
Class variables can have different names than the name of key in json. Just annotate using @SerialName
@SerialName("d_country")
var country : String = ""
Additional Utilities
There are few utilities also available out of box
Mapper
Mapper can convert a object to a map — straightforward.
val newYorkAsMap : Map<String,Any> = Mapper.map(newYork) // Mapping
val newNewYork = Mapper.unmap<Destination>(newYorkAsMap) //UnMapping
// Use Mapper.mapNullable() to support null values
ValueTransformer
Transformer allows custom transformation on each value of object.
object CustomTransformer : ValueTransformer() {
override fun transformStringValue(desc: KSerialClassDesc, index: Int, value: String): String =
value.toLowerCase()
override fun transformIntValue(desc: KSerialClassDesc, index: Int, value: Int): Int =
when(value) {
0 -> 1
else -> super.transformIntValue(desc, index, value)
}
}
val newDelhi = CustomTransformer.transform(delhi)
println(newDelhi)
// Destination(name=delhi, country=india, code=1)
// Strings are lowercase and integer code 0 became 1
There are more override methods for float, boolean etc in ValueTransform.
Custom Serialization
The lib allows you to define your own serialization scheme if required.
@Serializable
data class Country(var name : String = "", var hCode : Int = -1) {
@Serializer(forClass = Country::class)
companion object : KSerializer<Country>{
override fun load(input: KInput): Country {
TODO("Write your own scheme to handle input")
}
override fun save(output: KOutput, obj: Country) {
TODO("Write your own scheme to handle output")
}
override val serialClassDesc: KSerialClassDesc
get() = TODO()
}
}
Full doc here Kotlin/kotlinx.serialization
Code gist: https://gist.github.com/jitinsharma/8805cf63ba3b371b657531d55d3fd6c5
Thanks for reading!