jsonkotlinjacksonfasterxml

Jackson: Parse same key with different model


In Kotlin, I'm using jackson (fasterxml) for parsing an endpoint response (JSON). It looks similar to this:

{
...
steps: [
   {
      "status":200,
      "id":"A",
      "data":{
         "score":"10"
      }
   },
   {
      "status":200,
      "id":"B",
      "data": {
         "dateOfBirth":"10-09-1994",
         "fullname": "Peter",
         ...
      }
   },
   {
      "status":200,
      "id":"C",
      "data": {
         "dateOfBirth": {
             "value": "10-03-1993"
          },
         ...
      }
   },
]
...
}

My code looks like this:


...
response.readEntity(DocumentResponse::class.java)
...



@JsonIgnoreProperties(ignoreUnknown = true)
data class DocumentResponse @JsonCreator constructor(
    @JsonProperty("steps")
    val steps: List<StepResponse>
) 

data class StepResponse @JsonCreator constructor(
    @JsonProperty("status")
    val status: String,
    @JsonProperty("id")
    val id: String,
    @JsonProperty("data")
    val data: Data?
)

@JsonIgnoreProperties(ignoreUnknown = true)
class Data @JsonCreator constructor(
    @JsonProperty("dateOfBirth")
    val dateOfBirth: String?,
    @JsonProperty("fullName")
    val fullName: String?,
    @JsonProperty("dni")
    val dni: String?,
    ...
) 

The problem is that data can have multiple types, and I want to get this field from the JSON only when id is "B". When the JSON is retrieved and it's read, an error is thrown because it reads step with id "A" and dateOfBirth is not found, so it throws the following error:

MistmatchedInputException: Cannot deserialize instance of string out of START_OBJECT token ...

Is possible to ignore the field data if it doesn't match with the JSON format? I want to read the JSON but the only data that is relevant to me is the one related to a specific id!


Solution

  • I would say your best option is to use a custom deserializer for StepResponse:

    import com.fasterxml.jackson.databind.deser.std.StdDeserializer
    import com.fasterxml.jackson.core.JsonParser
    import com.fasterxml.jackson.databind.DeserializationContext
    import com.fasterxml.jackson.databind.JsonNode
    
    class StepResponseDeserializer constructor(vc: Class<*>? = null) : StdDeserializer<StepResponse>(vc) {
        override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): StepResponse {
            val node = jp.codec.readTree<JsonNode>(jp)
            val status = node["status"].asText()
            val id = node["id"].asText()
    
            val data = if ("B" == id) {
                val dataNode = node["data"]
                val dateOfBirth = dataNode["status"].asText()
                val fullName = dataNode["fullName"].asText()
                val dni = dataNode["dni"].asText()
                Data(dateOfBirth, fullName, dni)
            } else null
    
            return StepResponse(status, id, data)
        }
    }
    

    You can register the deserializer directly on the class as follows:

    @JsonDeserialize(using = StepResponseDeserializer.class)
    data class StepResponse @JsonCreator constructor(
        val status: String,
        val id: String,
        val data: Data?
    )
    

    As a side note, you can drop all the @JsonProperty annotations given that they are only needed if the Java/Kotlin property name does not match the JSON property name.