Search code examples
kotlinserializationjson-deserialization

How to prevent serializing null values using kotlin serialization


I'm using kotlinx-serialization-json

I have this class:

@Serializable
data class Event(
    @SerialName("temperature") val temperature: Float?,
    @SerialName("pressure") val pressure: Float?,
    @SerialName("humidity") val humidity: Float?,
)

and this call

Json.encodeToString(Event(temperature = 42.0f, pressure = null, humidity = 20.9f))

During serialization I receive such json:

{
    "temperature": 20.5,
    "pressure": null,
    "humidity": 20.9
}

but I would like to prevent serializing null values and receive this:

{
    "temperature": 20.5,
    "humidity": 20.9
}

It's a problem for me, because during serializing lengthy list of events I waste a lot of lines. Anyone's got an idea how to achieve this?

EDIT:

There is new simple way to achieve this: https://blog.jetbrains.com/kotlin/2021/09/kotlinx-serialization-1-3-released/#excluding-nulls


Solution

  • You can ignore all defaults and do something like this:

    @Serializable
    data class Event(
        @SerialName("temperature") val temperature: Float?,
        @SerialName("pressure") val pressure: Float? = null,
        @SerialName("humidity") val humidity: Float?,
    )
    
        val jsonMapper = Json { encodeDefaults = false}
        val body = jsonMapper.encodeToString(Event(temperature = 42.0f,pressure = null, humidity = 20.9f))
    
    

    Please, be aware of that, in the above case you are ignoring ALL defaults. If you want to ignore only null values you have to implement a custom serializer.

    For this example custom serializer will look like this:

    
    object EventSerializer: KSerializer<Event> {
        override fun deserialize(decoder: Decoder): Event {
            decoder.decodeStructure(descriptor) {
                var temperature: Float? = null
                var humidity:Float? = null
                var pressure: Float? = null
    
                while (true) {
                    when (val index = decodeElementIndex(descriptor)) {
                        0 -> temperature = decodeFloatElement(descriptor, 0)
                        1 -> pressure = decodeFloatElement(descriptor, 1)
                        2 -> humidity = decodeFloatElement(descriptor, 2)
                        CompositeDecoder.DECODE_DONE -> break
                        else -> error("Unexpected index: $index")
                    }
                }
               return Event(temperature, pressure, humidity)
            }
        }
    
        override fun serialize(encoder: Encoder, value: Event) {
            encoder.beginStructure(descriptor).run {
                value.temperature?.let { encodeStringElement(descriptor, 0, it.toString()) }
                value.pressure?.let { encodeStringElement(descriptor, 1, it.toString()) }
                value.humidity?.let { encodeStringElement(descriptor, 2, it.toString()) }
                endStructure(descriptor)
            }
        }
    
        override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Event") {
            element<String>("temperature")
            element<String>("pressure")
            element<String>("humidity")
    
        }
    }
    
    

    To use it -> @Serializable(with = EventSerializer::class)