Kotlin Deserialization - JSON Array to multiple different objects

2.6k views Asked by At

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

2

There are 2 answers

0
Dennis Anderson On BEST ANSWER

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

2
cactustictacs On

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!)