- Published on
Deserializing Collections with Empty JSON Objects with Jackson
- Authors
- Name
- Yair Mark
- @yairmark
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?>)