Home > front end >  Deserialize JSON with "non-stable" values using kotlin serialization
Deserialize JSON with "non-stable" values using kotlin serialization

Time:02-01

I have not very well structured JSON from BE (that I cannot change) which was formerly handled with moshi (custom adapter for the issue). Now I am trying to use pure kotlin serialization instead, but as said, JSON structure doesn't help me much.

{
   "foo": {
       "version":1,
       "mask": [
          {
             "values": [
                  { "bar": 1, ... }
              ]
           },
           {
             "values": [
                  "important text i guess"
              ]
           },
       ]
    }
}

Now as you can see my issue is with values that can contains both object as well as string. All this should be parsed into kotlin where values looks like this:

data class Values(
  val bar: Int? = null,
  val text: String? = null,
  ...
)

Theoretically I can change local implementation, e.g. split the values class or something. I've already tried to apply Polymorphic deserialization, but as I understand it was not able to recognise difference between two descendants of values without classDiscriminator set in JSON.

Any good advice?

Update

The polymorphic version I tried (in case I just made some error)

@Polymorphic
@Serializable
sealed class Values{
  
  @Serializable
  data class ObjectValues(
    val bar: Int? = null,
    ...
  )

  @Serializable
  data class TextValues(
    val text: String? = null
  )

}

and use it:

Json {
        ignoreUnknownKeys = true
        serializersModule = SerializersModule {
            polymorphic(Values::class) {
                subclass(Values.ObjectValues::class, Values.ObjectValues.serializer())
                subclass(Values.TextValues::class, Values.TextValues.serializer())
            }
        }

error:

Polymorphic serializer was not found for missing class discriminator ('null')
JSON input: {"bar":42,...}

Disclaimer I know that this would fix it all :)

"values": [ { "text":"important text i guess" } ]

CodePudding user response:

Having classDiscriminator is not a mandatory requirement for polymorphic deserialization. You can use content-based polymorphic deserialization in this case.

All you need is to define a JsonContentPolymorphicSerializer for Values class with some logic for exact subclass serializer selection:

object ValuesSerializer : JsonContentPolymorphicSerializer<Values>(Values::class) {
    override fun selectDeserializer(element: JsonElement) = when {
        element.jsonObject["values"]!!.jsonArray.first() is JsonObject -> ObjectValues.serializer()
        else -> TextValues.serializer()
    }
}

and wire it as a serializer for Values class:

@Serializable(with = ValuesSerializer::class)
sealed class Values

@Serializable
data class TextValues(val values: List<String>) : Values()

@Serializable
data class ObjectValues(val values: List<Bar>) : Values()

@Serializable
data class Bar(val bar: Int)

@Serializable
data class MyJson(val foo: Foo)

@Serializable
data class Foo(val version: Int, val mask: List<Values>)

No need for serializersModule:

val result = Json.decodeFromString<MyJson>(json) 
  •  Tags:  
  • Related