1

I'm using the 1.0.0 version of kotlin serialization but I'm stuck when I try to deserialize a "flexible" array.

From the Backend API that I don't control I get back an JSON Array that holds different types of objects. How would you deserialize them using kotlin serialization?

Example

This is the API's response

[
  {
    "id": "test",
    "person": "person",
    "lastTime": "lastTime",
    "expert": "pro"
  },
  {
    "id": "test",
    "person": "person",
    "period": "period",
    "value": 1
  }
]
@Serializable
sealed class Base {
  @SerialName("id")
  abstract val id: String
  @SerialName("person")
  abstract val person: String
}

@Serializable
data class ObjectA (
 @SerialName("id") override val id: String,
 @SerialName("title") override val title: String,
 @SerialName("lastTime") val lastTime: String,
 @SerialName("expert") val expert: String
) : Base()

@Serializable
data class ObjectB (
 @SerialName("id") override val id: String,
 @SerialName("title") override val title: String,
 @SerialName("period") val period: String,
 @SerialName("value") val value: Int
) : Base()


Performing the following code result in an error

println(Json.decodeFromString<List<Base>>(json))

error Polymorphic serializer was not found for class discriminator

3 Answers 3

1

When you say you don't control the API, is that JSON being generated from your code by the Kotlin serialization library? Or is it something else you want to wrangle into your own types?

By default sealed classes are handled by adding a type field to the JSON, which you have in your objects, but it's a property in your Base class. In the next example it shows you how you can add a @SerialName("owned") annotation to say what type value each class corresponds to, which might help you if you can add the right one to your classes? Although in your JSON example both objects have "type" as their type...

If you can't nudge the API response into the right places, you might have to write a custom serializer (it's the deserialize part you care about) to parse things and identify what each object looks like, and construct the appropriate one.

(I don't know a huge amount about the library or anything, just trying to give you some stuff to look at, see if it helps!)

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the reply! I want to let you know that type was purely imaginary field as example. I edited my question without type - This JSON comes from a Backend API. So I cant nudge the API response into the right place! I think I need to write a custom serializer then! I will have a look :)
If there's some identifier in the objects that explicitly labels it as one class or another (even if the name's wrong) that @SerialName annotation should work? If there's nothing to identify it and you basically have to deduce it from the fields, then yeah I think you're probably gonna need to write that logic yourself!
1

@cactustictacs solution came very close. He said that "By default sealed classes are handled by adding a type field to the JSON"

But because I didn't had a type property I needed a other field that decides which subclass it should be.

In Kotlin Serializer you can do that by

 val format = Json {
    classDiscriminator = "PROPERTY_THAT_DEFINES_THE_SUBCLASS"
 }
 val contentType = MediaType.get("application/json")
 val retrofit = Retrofit.Builder()
                .baseUrl(baseUrl)
                .client(client)
                .addConverterFactory(format.asConverterFactory(contentType))
                .build()

where in classDiscriminator you can enter the property that you want. Hope this helps other people in the future.

Comments

1

In case it's not clear to you even after reading the answers multiple times, I'd like to add a little more explanation.

Suppose you have a JSON string as the following.

val s1 = """
    [
        {
            "type": "string_class",
            "id": 1,
            "string": "hello"
        },
        {
            "type": "number_class",
            "id": 1,
            "number": 100
        }
    ]
    """.trimIndent()

Each item in the list looks different (i.e. one has "string" and the other has "number"), and "type" is what distinguishes them.

val json = Json {
    classDiscriminator = "type"
}

@Serializable
sealed class Base {
    abstract val id: Int

    @Serializable
    @SerialName("string_class")
    data class StringClass(
        override val id: Int,
        val string: String,
    ) : Base()

    @Serializable
    @SerialName("number_class")
    data class NumberClass(
        override val id: Int,
        val number: Int,
    ) : Base()
}

This works well, as long as each item has "type" that we can use to distinguish one class from the other. But if there is no "type" you can leverage, you'd need to create a custom Serializer.

val string1 = """
    [
        {
            "id": 1,
            "string": "Hello"
        },
        {
            "id": 1,
            "number": 100
        }
    ]
    """.trimIndent()

You'd need to check what fields are available, and choose the right Serializer.

object BaseSerializer : JsonContentPolymorphicSerializer<Base>(Base::class) {
    override fun selectDeserializer(
        element: JsonElement,
    ): DeserializationStrategy<Base> {
        val jsonObject = element.jsonObject
        return when {
            jsonObject.containsKey("string") -> Base.StringClass.serializer()
            jsonObject.containsKey("number") -> Base.NumberClass.serializer()
            else -> throw SerializationException("not supported")
        }
    }
}

@Serializable(BaseSerializer::class)
sealed class Base {
    abstract val id: Int

    @Serializable
    data class StringClass(
        override val id: Int,
        val string: String,
    ) : Base()


    @Serializable
    data class NumberClass(
        override val id: Int,
        val number: Int,
    ) : Base()
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.