1

I'm having problems trying to deserialize from json string into the correct object since I have to check 2 properties before deserialize to the correct class, i.e.:

{
  "org": "Company1",
  "event_type": "REGISTER",
  "day": "2021-01-01",
  "data": {
    "name": "Kyore",
    "age": 10
  }
},
{
  "org": "Company2",
  "event_type": "REGISTER",
  "day": "2021-01-01",
  "data": {
    "name": "Casky",
    "age": 12,
    "lastName": "Kyky"
  }
},
{
  "org": "Company2",
  "event_type": "DELETE",
  "day": "2021-01-01",
  "data": {
    "id": "1234-1234-1234",
    "reason": "user requested"
  }
}

I could make it work with the basic polymorphic deserialization using the JsonTypes as you can see here

The problem now is that I have this "org" field that might give me the same "event_type" value, but with a different data, so I need to find a way to first check if org is either Company1 or Company2 to call the correct JsonSubType, ie:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "org_id", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
@JsonSubTypes(value = [
    JsonSubTypes.Type(Company1Data::class, name = "Company1"),
    JsonSubTypes.Type(Company2Data::class, name = "Company2")
])
abstract class EventData {
    abstract fun validate()
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "event_type", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
@JsonSubTypes(value = [
    JsonSubTypes.Type(Cmp1RegisterData::class, name = "REGISTER")
])
abstract class Company1Data

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "event_type", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
@JsonSubTypes(value = [
    JsonSubTypes.Type(Cmp2RegisterData::class, name = "REGISTER"),
    JsonSubTypes.Type(DeleteData::class, name = "DELETE")
])
abstract class Company2Data

data class Cmp1RegisterData(
    val name: String,
    val age: Int
) :Company1Data()

data class Cmp2RegisterData(
    val name: String,
    val age: Int,
    val lastName: String
) :Company2Data()

data class DeleteData(
    val id: String,
    val reason: String
) :Company2Data()

The above code does not work since both CompanyData are abstract, so they can not be initialized, and using class won't work since I have the validate function in the "main" abstract class and I want to override it in the last class I'm deserializing (i.e.: Cmp2RegisterData)

BZKN
  • 1,499
  • 2
  • 10
  • 25
Kyore
  • 388
  • 2
  • 7
  • 29
  • I would say you have no other option than using a custom deserializer (take https://github.com/FasterXML/jackson-databind/issues/374 as reference). – João Dias Jan 06 '22 at 22:41
  • Yeah, I imagined some like a custom deserializer, but I couldn't find a way to call the "default deserializer" so I could use the JsonTypes notations to find which custom Data class it would use. When I set a custom deserialize, I have access to the JsonParser and the Context, I could do a jasonParser.getvalue("org") to check if it's "Company1" or "Company2", but I don't know how to call the `Company1Data` or `Company2Data` deserialization so it would check the JsonTypes – Kyore Jan 07 '22 at 19:34

1 Answers1

0

Interesting problem. It can be solved by using two semi-separate inheritance hierarchies: one for the Event and one for the EventData.

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "event_type",
    visible = false
)
@JsonSubTypes(
    JsonSubTypes.Type(value = RegisterEvent::class, name = "REGISTER"),
    JsonSubTypes.Type(value = DeleteEvent::class, name = "DELETE")
)
interface Event<T : EventData> {
    val org: String
    val event_type: String
    val day: String
    val data: T
}

data class RegisterEvent(
    override val org: String,
    override val day: String,
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
        property = "org"
    )
    @JsonSubTypes(
        JsonSubTypes.Type(value = Cmp1RegisterEventData::class, name = "Company1"),
        JsonSubTypes.Type(value = Cmp2RegisterEventData::class, name = "Company2")
    )
    override val data: RegisterEventData
) : Event<RegisterEventData> {
    override val event_type = "REGISTER"
}

data class DeleteEvent(
    override val org: String,
    override val day: String,
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
        property = "org"
    )
    @JsonSubTypes(
        JsonSubTypes.Type(value = Cmp2DeleteEventData::class, name = "Company2")
    )
    override val data: DeleteEventData
) : Event<DeleteEventData> {
    override val event_type = "DELETE"
}

interface EventData {
    fun validate() {
        System.out.printf("Validating %s\n", this.javaClass)
    }
}

interface DeleteEventData : EventData

data class Cmp2DeleteEventData(
    val id: String,
    val reason: String
) : DeleteEventData

interface RegisterEventData : EventData

data class Cmp1RegisterEventData(
    val name: String,
    val age: Int
) : RegisterEventData {
    override fun validate() {
        System.out.printf("Custom validator for %s\n", this.javaClass)
    }
}

data class Cmp2RegisterEventData(
    val name: String,
    val age: Int,
    val lastName: String
) : RegisterEventData {
    override fun validate() {
        System.out.printf("Another custom validator for %s\n", this.javaClass)
    }
}

class EventTest {

    @Test
    fun test() {

        val json = """
        [
            {
              "org": "Company1",
              "event_type": "REGISTER",
              "day": "2021-01-01",
              "data": {
                "name": "Kyore",
                "age": 10
              }
            },
            {
              "org": "Company2",
              "event_type": "REGISTER",
              "day": "2021-01-01",
              "data": {
                "name": "Casky",
                "age": 12,
                "lastName": "Kyky"
              }
            },
            {
              "org": "Company2",
              "event_type": "DELETE",
              "day": "2021-01-01",
              "data": {
                "id": "1234-1234-1234",
                "reason": "user requested"
              }
            }
        ]
    """.trimIndent()

        val objectMapper = ObjectMapper().registerModule(KotlinModule())
        val events: List<Event<*>> = objectMapper.readValue(json, object : TypeReference<List<Event<*>>>() {})

        Assertions.assertThat(events).containsExactly(
            RegisterEvent(
                "Company1",
                "2021-01-01",
                Cmp1RegisterEventData("Kyore", 10)
            ),
            RegisterEvent(
                "Company2",
                "2021-01-01",
                Cmp2RegisterEventData("Casky", 12, "Kyky")
            ),
            DeleteEvent(
                "Company2",
                "2021-01-01",
                Cmp2DeleteEventData("1234-1234-1234", "user requested")
            )
        )

        events.forEach { it.data.validate() }
    }
}
Mafor
  • 9,668
  • 2
  • 21
  • 36