Hello :wave: Is there a way to deserialize JSON re...
# serialization
d
Hello 👋 Is there a way to deserialize JSON response to just a hashmap? I have to handle the response where a field is an arbitrary JSON, e.g.
Copy code
@Serializable
data class GraphQLResponse<T>(
    val data: T? = null,
    val errors: List<GraphQLError>? = null,
    val extensions: Map<Any, Any>? = null // complains about missing serializer for Any
)
a
You can decode to JsonElement, which represents objects as Map<String, JsonElement> - basically a JSON AST
It will understand using JsonObject to be specifically an object at that point, I believe
d
yeah but exposing
JsonElement
to users seems like leaking internals vs just giving them the map
☝️ 1
*especially coming from Jackson where it just works
tried explicitly setting serializer on the field with no luck 😞
Copy code
@Serializable
data class GraphQLResponse<T>(
    val data: T? = null,
//    val errors: List<GraphQLError>? = null,
    @Serializable(with = GenericMapSerializer::class)
    val extensions: Map<Any, Any>? = null // complains about missing Any serializer
)

object GenericMapSerializer : KSerializer<Map<Any, Any>> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("GenericMap")

    override fun serialize(encoder: Encoder, value: Map<Any, Any>) {
        val jsonEncoder = encoder as JsonEncoder
        val jsonMap = value.entries.associate { entry ->
            entry.key.toString() to serializeAny(entry.value)
        }
        jsonEncoder.encodeJsonElement(JsonObject(jsonMap))
    }

    private fun serializeAny(value: Any?): JsonElement = when (value) {
        is Map<*, *> -> {
            val mapContents = value.entries.associate { mapEntry ->
                mapEntry.key.toString() to serializeAny(mapEntry.value)
            }
            JsonObject(mapContents)
        }
        is List<*> -> {
            val arrayContents = value.map { listEntry -> serializeAny(listEntry) }
            JsonArray(arrayContents)
        }
        is Number -> JsonPrimitive(value)
        is Boolean -> JsonPrimitive(value)
        else -> JsonPrimitive(value.toString())
    }

    override fun deserialize(decoder: Decoder): Map<Any, Any> {
        val jsonDecoder = decoder as JsonDecoder
        val element = jsonDecoder.decodeJsonElement()

        return if (element is JsonObject) {
            element.mapValues { deserializeJsonElement(it.value) }
        } else {
            throw SerializationException("cannot deserialize to a map as specified field is not an object")
        }
    }

    private fun deserializeJsonElement(element: JsonElement): Any = when (element) {
        is JsonObject -> {
            element.mapValues { deserializeJsonElement(it.value) }
        }
        is JsonArray -> {
            element.map { deserializeJsonElement(it) }
        }
        is JsonPrimitive -> element.toString()
    }
}
if anyone is looking for the similar solution it is actually very close to the above -> the difference is that we have to explicitly specify serializer on
Any
and not on a map, i.e.
Copy code
@Serializable
data class GraphQLResponse<T>(
    val data: T? = null,
//    val errors: List<GraphQLError>? = null,
    val extensions: Map<String, @Serializable(with = AnySerializer::class) Any>? = null
)

object AnySerializer : KSerializer<Any> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any")

    override fun serialize(encoder: Encoder, value: Any) {
        val jsonEncoder = encoder as JsonEncoder
        val jsonElement = serializeAny(value)
        jsonEncoder.encodeJsonElement(jsonElement)
    }

    private fun serializeAny(value: Any?): JsonElement = when (value) {
        is Map<*, *> -> {
            val mapContents = value.entries.associate { mapEntry ->
                mapEntry.key.toString() to serializeAny(mapEntry.value)
            }
            JsonObject(mapContents)
        }
        is List<*> -> {
            val arrayContents = value.map { listEntry -> serializeAny(listEntry) }
            JsonArray(arrayContents)
        }
        is Number -> JsonPrimitive(value)
        is Boolean -> JsonPrimitive(value)
        else -> JsonPrimitive(value.toString())
    }

    override fun deserialize(decoder: Decoder): Any {
        val jsonDecoder = decoder as JsonDecoder
        val element = jsonDecoder.decodeJsonElement()

        return deserializeJsonElement(element)
    }

    private fun deserializeJsonElement(element: JsonElement): Any = when (element) {
        is JsonObject -> {
            element.mapValues { deserializeJsonElement(it.value) }
        }
        is JsonArray -> {
            element.map { deserializeJsonElement(it) }
        }
        is JsonPrimitive -> element.toString()
    }
}