Published on

Deserializing Collections with Empty JSON Objects with Jackson

Authors

Today I was busy working with an unruly API that has very bad documentation. One of the issues I ran into when hitting the actual endpoint to test the integration was a response that looked like this:

{
  "name": "John",
  "addresses": [{}]
}

The Kotlin object looks as follows:

data class Person(val name: String, val addresses: List<Address>)

This did not work as I was getting errors where Jackson said something to the effect that a non-null field within Address was null.

So as a first attempt I tried making Address nullable inside the list - nope that did nothing.

I then tried making the entire field and generic inside it nullable:

data class Person(val name: String, val addresses: List<Address?>?)

But also no luck.

After Googling a tonne I discovered that Jackson has no default way of handling empty {} at least as far as I could see.

I had to make a custom deserializer in the end which looked like this:

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer

class EmptyObjectDeserializer<T> : JsonDeserializer<T?>() {
    override fun deserialize(parser: JsonParser, context: DeserializationContext): T? = if (parser.currentToken == JsonToken.START_OBJECT && parser.nextToken() == JsonToken.END_OBJECT) {
        null
    } else {
        context.readValue(parser, context.typeFactory.constructType(javaClass))
    }
}

I then registered this in my Jackson configuration as a SimpleModule against only the Person type (other types can be added as/when needed later):

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule

// ...


val builder = JsonMapper.builder()
        .addModules(KotlinModule.Builder().build(), JavaTimeModule())
        // ... your other config here
        .addModules(personModule())
// ...

private fun personModule(): SimpleModule {
        return SimpleModule().let {
            it.addDeserializer(RealPayContractDeleteResponseFailed::class.java, EmptyObjectDeserializer())
            it
        }
    }

To wrap this all up I had to make one last change to my Person class to make the addresses field allow nullable types but not be null in total and it all worked perfectly:

data class Person(val name: String, val addresses: List<Address?>)