Optional field vs null value in kotlinx.serialization

Viewed 876

How to distinguish between {data: null} and {} in kotlinx.serialization when deserializing JSON?

@Serializable
class MyClass(val data: String?)
2 Answers

You need a custom setter and a private boolean field to indicate whether the field value was touched. So something like:

@Serializable
class MyClass {
    private var isDataTouched = false

    val data: String
        set(value) {        
            field = value
            isDataTouched = true
        }
}

Note, you can't define the field in the default constructor.

You can take advantage of polymorphism coupled with JsonContentPolymorphicSerializer to distinguish what type of response you're deserializing:

@Serializable(with = MyResponseSerializer::class)
sealed class MyResponse

@Serializable
class MyEmptyClass : MyResponse()

@Serializable
class MyClass(val data: String?) : MyResponse()

// this serializer is reponsible for determining what class we will receive
// by inspecting json structure
object MyResponseSerializer : JsonContentPolymorphicSerializer<MyResponse>(MyResponse::class) {
    override fun selectDeserializer(element: JsonElement) = when {
        "data" in element.jsonObject -> MyClass.serializer()
        else -> MyEmptyClass.serializer()
    }
}

After that deserialize using MyResponse class and you will receive either MyEmptyClass or MyClass:

val e = Json.decodeFromString<MyResponse>("""{}""")             // returns MyEmptyClass object
val o = Json.decodeFromString<MyResponse>("""{"data": null}""") // returns MyClass object

println(Json.encodeToString(e)) // {}
println(Json.encodeToString(o)) // {"data": null}

// since sealed class is the parent we can have exhaustive when:
when(e) {
    is MyClass -> TODO("handle response with data")
    is MyEmptyClass -> TODO("handle empty response")
}
Related