Paul Meshkovsky
09/14/2023, 3:47 PMMarcin Wisniowski
09/14/2023, 4:13 PMPaul Meshkovsky
09/14/2023, 4:16 PMPaul Meshkovsky
09/14/2023, 4:17 PMCasey Brooks
09/14/2023, 4:19 PMAny
serializer actually have in it? It has no information available to work withCasey Brooks
09/14/2023, 4:20 PMList<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.Casey Brooks
09/14/2023, 4:23 PMPaul Meshkovsky
09/14/2023, 4:28 PMPaul Meshkovsky
09/14/2023, 4:30 PMPaul Meshkovsky
09/14/2023, 4:32 PMCasey Brooks
09/14/2023, 4:33 PMAny
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
Casey Brooks
09/14/2023, 4:35 PMPaul Meshkovsky
09/14/2023, 4:38 PMCasey Brooks
09/14/2023, 4:46 PMAny
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.Paul Meshkovsky
09/14/2023, 4:50 PMPaul Meshkovsky
09/14/2023, 4:50 PMPaul Meshkovsky
09/14/2023, 4:50 PMAnastasios Georgousakis
09/14/2023, 5:04 PMimport 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() }
Anastasios Georgousakis
09/14/2023, 5:07 PMdata class MyObject(
val id: String,
@Serializable(with = MapAnySerializer::class)
val data: Map<String, @Contextual Any?>? = null,
)
Marcin Wisniowski
09/14/2023, 6:38 PMSure 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.Paul Meshkovsky
05/29/2024, 6:00 PMPaul Meshkovsky
05/29/2024, 6:26 PM