I’ve been battling modeling serializable classes w...
# serialization
c
I’ve been battling modeling serializable classes with optionals and defaults. I understand that default property values don’t get encoded by default. I also know that one can enable
encodeDefaults
in the Json serialization class so defaults do get encoded. But, this has the side effect of encoding
null
into the json for properties that have been given default null values which is not desirable for communicating with some backend systems. So, I was wondering others have settled on for conventions or patterns to deal with this kind of thing. For example, lets considering the following example:
Copy code
interface MyInterface {
    val instruction: String
    val requiredField: String
}

@Serializable
data class MyDataClass(
    override val requiredField: String,
    val optionalField: String? = null
) : MyInterface {
    override val instruction: String = "doABarrelRoll"
}
Here, we’ve got MyInterface which describes the basic contract that all messages exchanged with a service should adopt. To see what the effects of
encodeDefault
being true/false, the following code
Copy code
val data = MyDataClass("foo")
println(Json.encodeToString(data))
println(Json { encodeDefaults = true }.encodeToString(data))
would output:
Copy code
{"requiredField":"foo"}
{"requiredField":"foo","optionalField":null,"instruction":"doABarrelRoll"}
But, we desire:
Copy code
{
  "requiredField": "foo",
  "instruction": "doABarrelRoll"
}
I know that a custom Serializer can be written for MyDataClass which could ignore encoding null values for properties. But, this seems that it would need to be done for all classes that incorporate optional properties. Apart from custom Serializers, has anyone used other strategies or structures for being able to encode defaults while ignoring nulls?
Jumping off from my original example, I discovered that I could switch to a var in the interface (
instruction
in this case) and in the implementing class use lateinit and make an assignment in an init block to get the desired output.
Copy code
interface MyOtherInterface {
    var instruction: String
    val requiredField: String
}

@Serializable
data class MyOtherDataClass(
    override val requiredField: String,
    val optionalField: String? = null
) : MyOtherInterface {
    override lateinit var instruction: String
    init {
        instruction = "doAnotherBarrelRoll"
    }
}
Copy code
val data = MyOtherDataClass("bar")
println(Json.encodeToString(data))
outputs:
Copy code
{
  "requiredField": "bar",
  "instruction": "doAnotherBarrelRoll"
}
So, I get the json I was hoping for, but this would require losing immutability on the
instruction
property and also require the author of each implementing class to know to follow this pattern.
p
There is an annotation (
@Required
) that you can put on your properties to stop them being omitted if they equal the default. (see https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#transient-properties)
🙌 2
1
c
For as much time as I’ve spent poring over the documentation, I don’t know how I missed
@Required
. Probably because the documentation looks like it only addresses decoding. But, it does appear to work for encoding too. You’re my hero, @pdvrieze
🎉 1
👍 1