kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Can't serialize class with Polymorphic annotation

Open MaxenceLvl opened this issue 1 year ago • 8 comments

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

MaxenceLvl avatar Nov 06 '23 15:11 MaxenceLvl

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

sandwwraith avatar Nov 06 '23 17:11 sandwwraith

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 ?

MaxenceLvl avatar Nov 06 '23 17:11 MaxenceLvl

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

sandwwraith avatar Nov 06 '23 17:11 sandwwraith

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

MaxenceLvl avatar Nov 06 '23 17:11 MaxenceLvl

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.

MaxenceLvl avatar Nov 06 '23 17:11 MaxenceLvl

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 avatar Nov 07 '23 12:11 sandwwraith

@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 ?

MaxenceLvl avatar Nov 07 '23 13:11 MaxenceLvl

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

sandwwraith avatar Nov 07 '23 15:11 sandwwraith