Kotlinx.serialization part4

Peter Nagy
4 min readFeb 3, 2021

This series is about Kotlinx serialization library.

In the Part1 I wrote about the basic usage of the Kotlinx.serialization.

In the Part2 I wrote about how can we configure the Kotlinx.serilaization.

In the Part3 I wrote about Custom serializers and data classes.

Now I would like to write about using Custom serializer with 3rd party classes.

In the Part3 I had a data class with Date and BigDecimal data objects. But the class what I used in the serialization was own class. It was easy to annotate it with the proper annotation (with custom serializer).

Ok, but what can we do, if we have class which is in a 3rd party lib and we have to serialize it ?

Let's see an example:

We want to write a persistent CookieJar and save and persist HTTP Cookies. (Most cases we are using REST API calls but it might be happen we have to call an endpoint with cookie(s) in that case we need to persist some cookies and put the proper ones in the request.)

In my case Cookie is in the OkHttp lib and it has a private constructor. If you want to create a Cookie you have to use Builder. There are 9 values what we have to persist (domain, expire at, name, path, etc…)

First we need a descriptor:

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Cookie") {
element<String>("domain")
element<Long>("expiresAt")
element<Boolean>("hostOnly")
element<Boolean>("httpOnly")
element<String>("name")
element<String>("path")
element<Boolean>("persistent")
element<Boolean>("secure")
element<String>("value")
}

Here are the fields which will be serialized (names and type of).

Serialization:

override fun serialize(encoder: Encoder, value: Cookie) {
val structure = encoder.beginStructure(descriptor)
structure.encodeStringElement(descriptor, INDEX0, value.domain)
structure.encodeLongElement(descriptor, INDEX1, value.expiresAt)
structure.encodeBooleanElement(descriptor, INDEX2, value.hostOnly)
structure.encodeBooleanElement(descriptor, INDEX3, value.httpOnly)
structure.encodeStringElement(descriptor, INDEX4, value.name)
structure.encodeStringElement(descriptor, INDEX5, value.path)
structure.encodeBooleanElement(descriptor, INDEX6, value.persistent)
structure.encodeBooleanElement(descriptor, INDEX7, value.secure)
structure.encodeStringElement(descriptor, INDEX8, value.value)
structure.endStructure(descriptor)
}

We need add an index value -> the given field in which place will be serialized. Domain field will be in first place serialized.

Deserialization:

override fun deserialize(decoder: Decoder): Cookie {
return decoder.decodeStructure(descriptor) {
var domain = ""
var expiresAt: Long? = null
var persistent = false
var hostOnly = false
var httpOnly = false
var name = ""
var path: String? = null
var secure = false
var value = ""
while (true) {
when (val index = decodeElementIndex(descriptor)) {
INDEX0 -> domain = decodeStringElement(descriptor, INDEX0)
INDEX1 -> expiresAt = decodeLongElement(descriptor, INDEX1)
....
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
val cookieBuilder = Cookie.Builder().name(name).value(value)
if (expiresAt != null && persistent) {
cookieBuilder.expiresAt(expiresAt)
}
if (hostOnly) {
cookieBuilder.hostOnlyDomain(domain)
} else {
cookieBuilder.domain(domain)
}
if (httpOnly) {
cookieBuilder.httpOnly()
}
if (secure) {
cookieBuilder.secure()
}
if (path != null) {
cookieBuilder.path(path)
}
cookieBuilder.build()
}
}

And when where we want to use serialization:

val serializedCookie = serializer.encodeToString(CookieSerializer, cookie)

EncodeToString and decodeFromString has an overloaded function where we can set a serializer. And with the help of it we can serialize the class. Of course if we want to use it in a class then we can use annotation.

@Serializable
data class FooData(val foo: String, @Serializable(with = CookieSerializer::class) val cookie: Cookie)

Ok, let's see the whole serializer:

object CookieSerializer : KSerializer<Cookie> {

private const val INDEX0 = 0
private const val INDEX1 = 1
private const val INDEX2 = 2
private const val INDEX3 = 3
private const val INDEX4 = 4
private const val INDEX5 = 5
private const val INDEX6 = 6
private const val INDEX7 = 7
private const val INDEX8 = 8

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Cookie") {
element<String>("domain")
element<Long>("expiresAt")
element<Boolean>("hostOnly")
element<Boolean>("httpOnly")
element<String>("name")
element<String>("path")
element<Boolean>("persistent")
element<Boolean>("secure")
element<String>("value")
}

override fun serialize(encoder: Encoder, value: Cookie) {
val structure = encoder.beginStructure(descriptor)
structure.encodeStringElement(descriptor, INDEX0, value.domain)
structure.encodeLongElement(descriptor, INDEX1, value.expiresAt)
structure.encodeBooleanElement(descriptor, INDEX2, value.hostOnly)
structure.encodeBooleanElement(descriptor, INDEX3, value.httpOnly)
structure.encodeStringElement(descriptor, INDEX4, value.name)
structure.encodeStringElement(descriptor, INDEX5, value.path)
structure.encodeBooleanElement(descriptor, INDEX6, value.persistent)
structure.encodeBooleanElement(descriptor, INDEX7, value.secure)
structure.encodeStringElement(descriptor, INDEX8, value.value)
structure.endStructure(descriptor)
}

@Suppress("ComplexMethod")
override fun deserialize(decoder: Decoder): Cookie {
return decoder.decodeStructure(descriptor) {
var domain = ""
var expiresAt: Long? = null
var persistent = false
var hostOnly = false
var httpOnly = false
var name = ""
var path: String? = null
var secure = false
var value = ""
while (true) {
when (val index = decodeElementIndex(descriptor)) {
INDEX0 -> domain = decodeStringElement(descriptor, INDEX0)
INDEX1 -> expiresAt = decodeLongElement(descriptor, INDEX1)
INDEX2 -> hostOnly = decodeBooleanElement(descriptor, INDEX2)
INDEX3 -> httpOnly = decodeBooleanElement(descriptor, INDEX3)
INDEX4 -> name = decodeStringElement(descriptor, INDEX4)
INDEX5 -> path = decodeStringElement(descriptor, INDEX5)
INDEX6 -> persistent = decodeBooleanElement(descriptor, INDEX6)
INDEX7 -> secure = decodeBooleanElement(descriptor, INDEX7)
INDEX8 -> value = decodeStringElement(descriptor, INDEX8)
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
val cookieBuilder = Cookie.Builder().name(name).value(value)
if (expiresAt != null && persistent) {
cookieBuilder.expiresAt(expiresAt)
}
if (hostOnly) {
cookieBuilder.hostOnlyDomain(domain)
} else {
cookieBuilder.domain(domain)
}
if (httpOnly) {
cookieBuilder.httpOnly()
}
if (secure) {
cookieBuilder.secure()
}
if (path != null) {
cookieBuilder.path(path)
}
cookieBuilder.build()
}
}
}

Thanks for reading my article! If you found this post useful? Kindly tap the 👏 button below and follow :) .

--

--