Search code examples
jsonkotlinjackson-databind

How can I just get a single field form a nested JSON object with Jackson annotations?


I get a JSON object like this:

{
    "id": "...",
    "Nested": {
        "nestedId": "..."
    }
}

There are lots of other attributes both in the top level object as in the Nested one.

I'm only interested in a dozen of fields of the top level object, and the Nested object's nestedId.

This code gets them into a Kotlin data class:

import com.fasterxml.jackson.annotation.*

@JsonIgnoreProperties(ignoreUnknown = true)
data class MainObject(
    @JsonProperty("id") val id: String,
    // ... more properties for other fields

    @JsonProperty("nested") val nested: Nested?,
) {
    val nestedId: String? get() = nested?.id
}

@JsonIgnoreProperties(ignoreUnknown = true)
data class Nested(
    @JsonProperty("id") val id: String
)

and I can access the nested object's id nicely with n.nestedId.

I'd still like to get rid off the extra Nested class.

How can I use annotations to only deserialize the nested id instead of the whole object?

I've seen custom a JsonDeserializer, but that seems like it's almost more code and complexity than my little extra class.

I guess I could use readTree and path, but then I'll have to map all the other fields manually.


Solution

  • Try something like this

    @Test
    fun `unpack nested field`() {
        val mapper = ObjectMapper().registerKotlinModule()
        val string = """{"id": "main_id", "nested": {"id": "nested_id"}}"""
        val mainObject = mapper.readValue<MainObject>(string)
        assertEquals("nested_id", mainObject.nestedId)
    }
    
    data class MainObject(
        val id: String,
        var nestedId: String? = null,
    ) {
        @JsonProperty("nested")
        private fun unpackNested(nested: Map<String, Any>) {
            this.nestedId = nested["id"] as String
        }
    }
    

    In your example nested (inner) field is nullable, but if the nestedId is not nullable, then I would suggest move nestedId out of constructor and make it lateinit:

    data class MainObject(
        val id: String
    ) {
        lateinit var nestedId: String
    
        @JsonProperty("nested")
        private fun unpackNested(nested: Map<String, Any>) {
            nestedId = nested["id"]!! as String
        }
    }