Hi all, this has been bothering me for the last fe...
# serialization
b
Hi all, this has been bothering me for the last few hours: I have this JSON structure, and I'm trying to use Kotlin Serialization to map this to some classes, but I have no luck. In the below structure, I have
value
attribute. The value attribute is sometimes a String, sometimes sometimes a Map/Object:
Copy code
{
  "claims": [
    {
      "name": "txn",
      "type": "new",
      "scope": "profile"
    },
    {
      "name": "name",
      "value": "Title Given Family",
      "type": "new",
      "scope": "profile"
    },
    {
      "name": "address",
      "value": {
        "country": "Some Country",
        "locality": "City",
        "postal_code": "1000",
        "region": "Region",
        "street_address": "Street Address"
      },
      "type": "new",
      "scope": "profile"
    }
  ]
}
I've tried creating a custom KSerializer for the property, something like:
Copy code
@Serializable
data class Claim(
    val name: String,
    val type: String,
    val scope: String,
    @Serializable(with = ClaimValueSerializer::class)
    val value: Any?
)
But no luck so far, I'm getting JSON parsing issues, and I have a hard time to map both a map and string to the claim value. Can anyone give me a pointer on how to approach this?
b
Hi Björn, thanks for that, but it seems like this is based on the content of the JSON, and I'm not sure what keys to expect. I did feed my question into GPT btw, and this is what it came up with:
Copy code
object ClaimValueSerializer : KSerializer<Any> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("value", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Any) = TODO()
    override fun deserialize(decoder: Decoder): Any {
        val jsonElement = (decoder as? JsonDecoder)
            ?.decodeJsonElement()
            ?: throw IllegalStateException("This class can be loaded only by JSON")

        return when (jsonElement) {
            is JsonPrimitive -> jsonElement.content
            is JsonObject -> jsonElement.toMap()
            else -> throw IllegalArgumentException("Unsupported type")
        }
    }
}
And it actually works. I'm not sure if this is the "right" way of doing it, but happy to go forward with this.
a
as a quickfix you could decode
value
to a JsonElement, and then manually decode it later on in the code
Copy code
@Serializable
data class Claim(
    // ...
    val value: JsonElement? = null
)
b
Ah, that's also supported? I'd be okay with that too. Thanks Adam!
a
I see in the first element in your example there's no
"value": null
, so it'd be important to have a default value
> Ah, that's also supported? I'd be okay with that too. Thanks Adam! Np! And if you manually do some JsonElement -> YourCustomClass logic for now, then later it'd be easy to convert that into a custom serializer using JsonContentPolymorphicSerializer https://github.com/Kotlin/kotlinx.serialization/blob/v1.6.3/docs/json.md#content-based-polymorphic-deserialization
👍 1
b
I see in the first element in your example there's no
"value": null
, so it'd be important to have a default value
ah yes, that's a real example actually, I left it in to demonstrate the nullability of this attribute.
a
there's a subtle difference between nullable and optional :) It tripped me up at first too
image.png
b
You sure about
email: String?
being not optional? Or does the above only apply to the default Serializable properties? (in my json, the value is missing, but even without a default value, it works fine, but this could be caused by my Custom serializer...)
a
I will double check...
I get an exception when I run this code (which is correct & expected, because
email
has no default value it's required)
Copy code
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class ContactDetails(
  val name: String,
  val email: String?,
  val active: Boolean = true,
  val phoneNumber: String? = null,
)

fun main() {
  val cd = Json.decodeFromString(ContactDetails.serializer(), """
    {
      "name": "x"
    }
  """.trimIndent())
  println(cd)
}
Copy code
Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'email' is required for type with serial name 'ContactDetails', but it was missing at path: $
👍 1
b
Thanks for checking!
a
no probs!