Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
aaziz993 opened this issue May 20, 2024 · 6 comments
Closed

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

aaziz993 opened this issue May 20, 2024 · 6 comments

Comments

@aaziz993
Copy link

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
Copy link
Owner

@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
Copy link
Author

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
Copy link
Owner

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
Copy link
Author

Understood. Thank you for support.

@pdvrieze
Copy link
Owner

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
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants