Håkon Pettersen
08/23/2023, 12:48 PM[
{
"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?Adam S
08/23/2023, 1:04 PMval 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.
@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åkon Pettersen
08/23/2023, 4:50 PMkotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for class discriminator 'PLAN_STOPPED2'
. How can I solve this?Adam S
08/23/2023, 5:02 PMHåkon Pettersen
08/23/2023, 6:06 PM@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:
@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:
@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:
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)
}
}
Håkon Pettersen
08/23/2023, 6:12 PM