pdvrieze / xmlutil

XML Serialization library for Kotlin
https://pdvrieze.github.io/xmlutil/
Apache License 2.0
363 stars 30 forks source link

Is @Contextual supported, because I get error while I use it. #208

Closed aaziz993 closed 1 month ago

aaziz993 commented 1 month ago

In my use case I need to use @Contextual on Java datetime, BigInteger, BigDecimal, UUID and Any types with custom serializers, but it leads to an error.

pdvrieze commented 1 month ago

@aaziz993 There is nothing in the library that should block contextual serializers. You may need to look at kotlinx.serialization documentation on how to do it properly (Any is polymorphic so contextual isn't really the right way). If you can provide some code sample/error messages I may be able to help a bit.

The suggested way to have custom serializers is to just use @Serializable(MyBISerializer::class) BigInteger on the type use. Another way is to put this in a type alias. This tells the compiler plugin which serializer to use (at compile time).

aaziz993 commented 1 month ago

Sorry for late response, It seems that when I use one @Contextual property it is ok, but if more I get an error. In Json it is fine. This is the test code:

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.SerialKind
import kotlinx.serialization.descriptors.buildSerialDescriptor
import kotlinx.serialization.encodeToString
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import nl.adaptivity.xmlutil.core.impl.multiplatform.name
import nl.adaptivity.xmlutil.serialization.XML
import java.math.BigInteger
import java.util.UUID
import kotlin.reflect.KClass

abstract class PrimitiveSerializer<T : Any>(
kClass: KClass<T>,
val parser: (String) -> T,
val toString: (T) -> String = { it.toString() },
) : KSerializer<T> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor(
kClass.name,
PrimitiveKind.STRING,
)

    override fun serialize(
        encoder: Encoder,
        value: T,
    ) {
        encoder.encodeString(toString(value))
    }

    override fun deserialize(decoder: Decoder): T {
        return parser(decoder.decodeString())
    }
}

class JavaUUIDSerializer : PrimitiveSerializer<UUID>(UUID::class, UUID::fromString)
typealias JavaUUID =
@Serializable(with = JavaUUIDSerializer::class)
UUID

class JavaBigIntegerSerializer : PrimitiveSerializer<BigInteger>(
BigInteger::class,
::BigInteger,
)

typealias JavaBigInteger =
@Serializable(with = JavaBigIntegerSerializer::class)
BigInteger

// Example Contextual any serializer which for test purposes serializes integer value
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
class ExampleAnyIntSerializer : KSerializer<Any> {
override val descriptor: SerialDescriptor = buildSerialDescriptor(Any::class.name, SerialKind.CONTEXTUAL)

    override fun deserialize(decoder: Decoder): Any = decoder.decodeInt()

    override fun serialize(
        encoder: Encoder,
        value: Any,
    ) {
        encoder.encodeInt(value as Int)
    }
}

@Serializable
data class Test(
@Contextual
val any1: Any,
@Contextual
val any2: Any,
)

val serializers =
SerializersModule {
contextual(ExampleAnyIntSerializer())
}

val xmlDefault =
XML(serializers)

val jsonDefault =
Json {
serializersModule = serializers
}

@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
fun main() {
println("-----------------------------------SERIALIZE WITH XML------------------------------------------------")
try {
val xmlSerializedTest2 = xmlDefault.encodeToString(Test(100, 109))
println(xmlSerializedTest2)
val xmlDeserializedTest2: Test = xmlDefault.decodeFromString(xmlSerializedTest2)
println(xmlDeserializedTest2)
} catch (e: Throwable) {
println(e)
}

    println("-----------------------------------SERIALIZE WITH JSON------------------------------------------------")
    try {
        val jsonSerializedTest2 = jsonDefault.encodeToString(Test(100, 109))
        println(jsonSerializedTest2)
        val jsonDeserializedTest2: Test = jsonDefault.decodeFromString(jsonSerializedTest2)
        println(jsonDeserializedTest2)
    } catch (e: Throwable) {
        println(e)
    }
}

And this is the error:

-----------------------------------SERIALIZE WITH XML------------------------------------------------
<Test><Test>100</Test><Test>109</Test></Test>
kotlinx.serialization.MissingFieldException: Field 'any1' is required for type with serial name 'ai.tech.Test', but it was missing
-----------------------------------SERIALIZE WITH JSON------------------------------------------------
{"any1":100,"any2":109}
Test(any1=100, any2=109)
pdvrieze commented 1 month ago

Ok. The problem appears that for serialization the inner values are serialized using the Test tag, rather than something that indicates the parameter name (or type). This is clearly an issue with @Contextual support.

aaziz993 commented 1 month ago

Understood. Thank you for support.

pdvrieze commented 1 month ago

I've implemented contextual serialization. It will be supported in 0.90.0-RC1 (out soon).

There is however one error in your code. The descriptor of ExampleAnyIntSerializer can not be CONTEXTUAL itself (contextual means it will delegate to an actual serializer derived at runtime), as the xml format must know what the actual type of the serialization (PrimitiveKind.INT) will be (without actually serializing/deserializing it).

You also want to note that the way to support serializers types that don't natively support it is to use @Serializable(with=Serializer::class) annotation (resolved at compile time), not to use @Contextual (resolved at serialization time).

aaziz993 commented 1 month ago

I will incorporate your feedback and look forward to 0.90.0-RC1.