Kotlin Serialization to JSON: How to ignore actual field type

Viewed 5909

I am new to Kotlin and even newer to Kotlin Serialization and I am trying to convert a Kotlin data class to JSON. Look at the following sample code:

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JSON

fun main(args: Array<String>) {
    println(JSON.stringify(BaseClass1("abc")))
    println(JSON.stringify(BaseClass2("abc")))
}

@Serializable
data class BaseClass1(
        val a: String
)

@Serializable
data class BaseClass2(
        val a: Any
)

The output is:

{"a":"abc"}
{"a":["kotlin.String","abc"]}

So I save a String in a variable of the type Any (which should be perfectly valid). However, this results in the serialized value being an array of the type and the value. This also happens with other classes and subclasses (when I extend the field's type for example).

How can I get stringify to always use the value, regardless of the field's actual type? I want the second stringify result of the program above to match the first one.

2 Answers

I managed to accomplish this using a custom serializer.

The only downside is that as I don't save the type, I cannot deserialize the object again, but I only need to serialize in my program.

@Serializable
data class BaseClass2(
        @Serializable(with = PlainObjectSerializer::class) val a: Any
)


object PlainObjectSerializer : KSerializer<Any> {
    override val serialClassDesc: KSerialClassDesc
        get() = SerialClassDescImpl("") //PlainObjectDesc

    override fun save(output: KOutput, obj: Any) {
        val saver = serializerByValue(obj, output.context)
        output.writeSerializableValue(saver, obj)
    }

    override fun load(input: KInput): Any {
        throw Exception("This serializer can only be used for serialization!")
    }
}

EDIT: As @Nikky suggested, this code can be further simplified by utilizing the serialization plugin:

@Serializer(forClass = BaseClass2::class)
object PlainObjectSerializer : KSerializer<Any> {
    override fun save(output: KOutput, obj: Any) {
        val saver = serializerByValue(obj, output.context)
        output.writeSerializableValue(saver, obj)
    }
}

you may simplify your solution by annotating the serializer with @Serializer(forClass=BaseClass1:class) then you can leave out the load and serialClassDesc overrides .. the plugin will generate the defaults for you

Related