Closed aaziz993 closed 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).
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)
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.
Understood. Thank you for support.
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).
I will incorporate your feedback and look forward to 0.90.0-RC1.
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.