I have a json-object with the following structure:...
# serialization
h
I have a json-object with the following structure:
Copy code
[
{
 "type": "PLAN_CALCULATED",
 "data": {
     "hoursToCharge": "4"
     "readyTime": "15:00"
    },
},
{
 "type": "PLAN_STOPPED",
 "data": {
     "reason": "connection"
    },
},
]
Normally, if the "type" attribute were contained within the "data" object, I would employ polymorphism and create an interface with "type" as the discriminator. However, in this scenario, the "type" field is outside the "data" object. How can I use this externally-defined "type" as the discriminator for deserialization?
a
I see two options 1. write a JsonContentPolymorphicSerializer Pro: less code, Con: perhaps harder to reason about, there's a chance of missing a 'type' 2. Create a sealed-interface with a
val data: NestedData
property.
NestedData
is also a sealed interface, that will match the structure for each 'type'. Then, for each 'type' create a specific subclass.
Copy code
@Serializable
sealed interface DataHolder {
  val data: NestedData

  @Serializable
  @SerialName("PLAN_CALCULATED")
  data class WithPlanCalculated(
    override val data: NestedData.PlanCalculated,
  ): DataHolder
  // ...
}

@Serializable
sealed interface NestedData {
  @Serializable
  data class PlanCalculated(...): NestedData
  // ...
}
Pro: more explicit, easier to reason about, Con: more code Personally I'd lean towards #2. More explicitness is usually best when it comes ot KxS. But it depends on the situation.
h
@Adam S #2 seems is working great, simple to implement for my use-case, thanks! Another question. Lets say that a unknown type is received. In my use-case I would like to simply ignore the value, but currently I would get an exception:
kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for class discriminator 'PLAN_STOPPED2'
. How can I solve this?
a
Nice! For an unknown type, there's a workaround in this issue
h
This is what I have come up with so far. Undefined types will be serialized to `Unknown`: enum and serializer:
Copy code
@Serializable(with = EvChargingEventTypeSerializer::class)
enum class EvChargingEventType {
    PLAN_CALCULATED,
    PLAN_STOPPED,
    UNKNOWN
}

object EvChargingEventTypeSerializer : KSerializer<EvChargingEventType> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("type", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: EvChargingEventType) {
        encoder.encodeString(value.name)
    }

    override fun deserialize(decoder: Decoder): EvChargingEventType {
        val value = decoder.decodeString()
        return EvChargingEventType.values().find { it.name == value } ?: EvChargingEventType.UNKNOWN // Default value if not found
    }
}
nested data:
Copy code
@Serializable
sealed interface NestedData {
    @Serializable
    data class PlanCalculated(
        val hoursToCharge: String,
        val readyTime: String,
    ) : NestedData

    @Serializable
    data class PlanStopped(
        val reason: String,
    ) : NestedData
}
data model interface:
Copy code
@Serializable
@JsonClassDiscriminator("type")
sealed interface EventLog {
    val type: EvChargingEventType
    val time: String

    @SerialName("PLAN_CALCULATED")
    @Serializable
    data class PlanCalculated(
        override val type: EvChargingEventType,
        override val time: String,
        val data: NestedData.PlanCalculated,
    ) : EventLog

    @SerialName("PLAN_STOPPED")
    @Serializable
    data class PlanStopped(
        override val type: EvChargingEventType,
        override val time: String,
        val data: NestedData.PlanStopped,
    ) : EventLog

    @Serializable
    data class Unknown(
        override val time: String,
        override val type: EvChargingEventType,
    ) : EventLog
}
test:
Copy code
class SerialTest {

    private val jsonData = """
        [
          {
            "type": "PLAN_CALCULATED",
            "time": "2023-08-16T12:00:43.246Z",
            "data": {
              "hoursToCharge": "4",
              "readyTime": "15:00"
            }
          },
          {
            "type": "PLAN_STOPPED",
            "time": "2023-08-16T12:00:43.246Z",
            "data": {
              "reason": "connection"
            }
          },
          {
            "type": "UNDEFINED"
            "time": "2023-08-16T12:00:43.246Z"
          }
        ]
    """.trimIndent()

    @Test
    fun test() {
        val eventSerializationModule = SerializersModule {
            polymorphic(EventLog::class) {
                default { EventLog.Unknown.serializer() }
            }
        }

        val json = Json {
            ignoreUnknownKeys = true
            serializersModule += eventSerializationModule
        }
        val data = json.decodeFromString(ListSerializer(EventLog.serializer()), jsonData)

        println(data)
    }
}
@Adam S As far as I am aware the only way this can fail is if the data-object differs from what is expected based on the type.