Is there a Serializer for Any ?
# ktor
p
Is there a Serializer for Any ?
🚫 1
m
You could write your own.
p
Sure but is this one of the fundamental objects in Kotlin
It's part of core Kotlin is it not
c
But Kotlin is strongly typed, and kotlinx-serialization works by code generation. What could a generated
Any
serializer actually have in it? It has no information available to work with
I’m guessing you’re trying to do something like deserialize a
List<Any>
or
Map<String, Any>
, perhaps for handling arbitrary JSON. My suggestion in this case is to work directly with the provided
JsonElement.serializer()
, which gives you direct access to the JSON tree.
Alternatively, you’re deserializing a list of data whose items might be one of serveral different types. This use-case is what polymorphic serialization or custom serializers are for
p
So Jackson has support for Any Serialization can't
I am trying to do PATCH where value can be Any
I have 50 classes each class can have over 100 fields in it that just creates too much boiler plate code
c
But what is
Any
supposed to be? Presumably, it is limited in the types that your API allows for that value. Kotlinx.serialzation requires to you define what those types are. It doesn’t know how to do anything else with it. Again, if you’re trying to accept arbitrary JSON values through your API,
Any
is not the right class.
Any
doesn’t represent anything meaningful. What you want is just the raw JSON value, which is
JsonElement
You might also consider writing a custom KSP plugin to generate all the boilerplate for you
👀 1
p
Ok I see the dilemma assuming we are dealing with Data in that case Any should support Single value @AnySingle or if complex should it be equivalent to Map/Dictionary similar to Jacksons JsonAny ?
c
I understand what you want from an
Any
serialization, but kotlinx.serialzation simply does not work that way, because it is based on code generation. It cannot convert an arbitrary JSON structure to an instance of
Any
, or set it to an arbitrary class type, because it needs to know at compile-time what type it is deserializing to. Otherwise, it doesn’t have any way of knowing which JSON values match up with which class properties. If you want the library to just give you the raw data from the PATCH request body so you can do with it whatever you need, then you need to deserialize it to
JsonElement
first. But if you’re expecting it to automatically update one of the arbitrary values of a class based on the field name in JSON, you’ll need to write the boilerplate to make that happen, or else use another reflection-based library that is able to match the value to a field by its name at runtime.
p
Ok thank you
And took care of the PATCH using reflection
this is more on the client end
a
To serialise/deserialise Map<String, Any> I’m doing this
Copy code
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.json.*
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import java.time.LocalDate
import java.time.temporal.TemporalAccessor

object MapAnySerializer : KSerializer<Map<String, Any?>> {
    @Serializable
    private abstract class MapAnyMap : Map<String, Any?>

    override val descriptor: SerialDescriptor = MapAnyMap.serializer().descriptor
//    val descriptor: SerialDescriptor = Map.serializer().descriptor

    override fun deserialize(decoder: Decoder): Map<String, Any?> {
        if (decoder is JsonDecoder) {
            val jsonObject = decoder.decodeJsonElement() as JsonObject
            return jsonObject.toPrimitiveMap()
        } else {
            throw NotImplementedError("Decoder $decoder is not supported!")
        }
    }

    override fun serialize(encoder: Encoder, value: Map<String, Any?>) {
        if (encoder is JsonEncoder) {
            encoder.encodeJsonElement(value.toJsonElement())
        } else {
            throw NotImplementedError("Encoder $encoder is not supported!")
        }
    }

}


fun Any?.toJsonElement(): JsonElement = when (this) {
    null -> JsonNull
    is JsonElement -> this
    is Number -> JsonPrimitive(this)
    is String -> JsonPrimitive(this)
    is Boolean -> JsonPrimitive(this)
    is Enum<*> -> JsonPrimitive(this.toString())
    is TemporalAccessor -> JsonPrimitive(this.toString())
    is Instant,
    is LocalDate,
    is LocalDateTime -> JsonPrimitive(this.toString())
    is Array<*> -> this.toJsonElement()
    is Iterable<*> -> this.toJsonElement()
    is Map<*, *> -> this.toJsonElement()
    else -> throw IllegalStateException("Can't serialize unknown type: $this")
}

fun Iterable<*>.toJsonElement() =
    JsonArray(this.map { it.toJsonElement() })

fun Array<*>.toJsonElement() =
    JsonArray(this.map { it.toJsonElement() })

fun Map<*, *>.toJsonElement() =
    JsonObject(this.map { (key, value) -> key as String to value.toJsonElement() }.toMap())

fun JsonElement.toPrimitive(): Any? = when (this) {
    is JsonNull -> null
    is JsonObject -> this.toPrimitiveMap()
    is JsonArray -> this.toPrimitiveList()
    is JsonPrimitive -> {
        if (isString) {
            contentOrNull
        } else {
            booleanOrNull ?: longOrNull ?: doubleOrNull
        }
    }
    else -> null
}

fun JsonObject.toPrimitiveMap(): Map<String, Any?> =
    this.map { (key, value) -> key to value.toPrimitive() }.toMap()

fun JsonArray.toPrimitiveList(): List<Any?> =
    this.map { it.toPrimitive() }
😍 1
you can use the serialiser in any property you will receive arbitrary JSON
Copy code
data class MyObject(
  val id: String,
  @Serializable(with = MapAnySerializer::class)
  val data: Map<String, @Contextual Any?>? = null,
)
😍 1
m
Sure but is this one of the fundamental objects in Kotlin
I said you can write your own because only you can know how would you like to serialize it (e.g. by serialiazing it as the string "unknown").
Any
is a type that could be literally `any`thing, so unless you use reflection it's impossible to have a serialiazer for it that returns anything useful without knowing project-specific requirements.
p
Thank you @Anastasios Georgousakis this is great help
466 Views