kotlinx.serialization
kotlinx.serialization copied to clipboard
Can't serialize class with Polymorphic annotation
Describe the bug
I can't serialize my Json into desired class with polymorphic annotation When I test it, I got this error :
Polymorphic serializer was not found for missing class discriminator ('null')
To Reproduce
Here my Serializer
@Serializable
data class ReceivedJSON (val collection: List<Collection>) {
@Serializable
data class Collection (
val resource: Resource
) {
@Serializable
data class Resource (
val type: DataType,
val values: List<ValueElementJSON>
)
}
}
@Serializable
data class DataType (
val id: Long,
val name: String,
@SerialName("value_type")
val valueType: String,
@SerialName("consumer_type_id")
val consumerTypeID: Long,
val unique: Boolean
)
@Polymorphic
@Serializable
abstract class ValueElementJSON
@Serializable
@SerialName("text")
data class TextJSON(
val value: String
): ValueElementJSON()
@Serializable
@SerialName("id")
data class IdJSON (
val id: Int
) : ValueElementJSON()
@Serializable
@SerialName("bool")
data class BoolJSON(
val value: Boolean
) : ValueElementJSON()
@Serializable
@SerialName("product_brand_id")
data class ProductJSON (
val id: Int,
val image: Image,
val lib: String,
val averageNote: Double
)
When unique is true, that mean the values array count only 1 element. Otherwise I can have 0 to n element.
Here my JSON file to test :
{
"collection": [
{
"resource": {
"type": {
"id": 1,
"name": "...",
"value_type": "id",
"consumer_type_id": 18,
"unique": true
},
"values": [
{
"id": 2
}
]
}
},
{
"resource": {
"type": {
"id": 20,
"name": "...",
"value_type": "text",
"consumer_type_id": 18,
"unique": true
},
"values": [
"string"
]
}
},
{
"resource": {
"type": {
"id": 13,
"name": "...",
"value_type": "bool",
"consumer_type_id": 1,
"unique": true
},
"values": [
true
]
}
},
{
"resource": {
"type": {
"id": 16,
"name": "...",
"value_type": "product_brand_id",
"consumer_type_id": 1,
"unique": false
},
"values": [
{
"id": 79,
"image": {
"filename": "...",
"large": "...",
"medium": "...",
"small": "...",
"source": "...",
"thumbor": "..."
},
"lib": "string"
}
]
}
},
]
}
Expected behavior
I would like to have my list of ValueElementJSON depending on "value_type"
Environment
- Kotlin version: 1.9.0
- Library version: 1.5.1 it's embed as plugin with jetbrains : org.jetbrains.kotlin.plugin.serialization
- Kotlin platforms: JVM
- Gradle version: 8.4
- IDE version (if bug is related to the IDE): Android Studio Giraffe 2022.3.1 Patch 2 Build #AI-223.8836.35.2231.10811636
I guess you want
val json = Json { classDiscriminator = "value_type" }
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#class-discriminator-for-polymorphism
I already have this :
private var client = HttpClient(engine) {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
classDiscriminator = "value_type"
})
}
}
I try this but always the same error when I try to parse my Json file
Maybe this is due to the deepest level of my wanted discriminator ?
Ah, you want to polymorphically deserialize "values": [...] but the values themselves do not have value_type in them — it is stored outside in the DataType.
In that case, you should use a custom serializer, probably a content-based one: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#content-based-polymorphic-deserialization
Otherwise I can try with this :
JsonContentPolymorphicSerializer<T>
But I don't know how it can be used in my case.
First time for me with polymorphism
I don't know on which object I can call the custom serializer, because we can go throw element.jsonObject.keys->
override fun selectDeserializer(element: JsonElement) = when {
"text" in element.jsonObject.keys
else ->
}
In my case the top object to do it is ConsumerDataType, no ? Otherwise I don't understand to use it.
It has to be the least common ancestor that contains both type key and polymorphic data, so Resource in your case. You take type from DataType and then deserialize values knowing that type.
@sandwwraith
object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
override fun selectDeserializer(element: JsonElement) = when {
"owner" in element.jsonObject -> OwnedProject.serializer()
else -> BasicProject.serializer()
}
}
So here I replace Projet by Resource, that's right?
But how I can call this serializer on my Resource Class ? Or I can use this on top Json element like Collection and go through each JsonElement trying to parse them in my desired class ?
But how I can call this serializer on my Resource Class
Check out https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#specifying-serializer-on-a-property