João Horta Alves
04/29/2024, 8:11 AMcall.receive<NonEmptyList<...>>
in ktor with arrow-core-serialization
.
Meaning that the mechanism for parsing JSON relies on the @Serializable
annotation, and since NonEmptyList is not annotated I cannot just parse it directly, even using @file:UseSerializers(NonEmptyListSerializer::class)
on the file?
Also posted to stack overflow.João Horta Alves
04/29/2024, 8:13 AMcall.receive
though it works with plain Json.decodeFromString
simon.vergauwen
04/29/2024, 8:38 AMNonEmptyListSerializer
. @file
only works when you add it in a file that defines the data class
like you said.
So for KotlinX to find the polymorphic serializer, you need to use KotlinX Serializers Module. https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#merging-library-serializers-modules
So in Ktor:
install(ContentNegotation) {
json {
serializersModule = SerializersModule {
}
}
}
João Horta Alves
04/29/2024, 9:06 AMJoão Horta Alves
04/29/2024, 9:14 AMNonEmptyListSerializer
simon.vergauwen
04/29/2024, 9:16 AMList<MyClass>
as well, since List
is not annotated with @Serializable
and/or because A
cannot be verified to be @Serialiazer
without the SerializersModule
. Sorry, I am not an expert on this area but afaik it's related to the runtime reflection lookupJoão Horta Alves
04/29/2024, 9:54 AMinstall(ContentNegotiation) {
json(Json {
serializersModule =
SerializersModule { contextual(NonEmptyList::class) { args -> NonEmptyListSerializer(args[0]) } }
})
}
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization-and-generic-classesJoão Horta Alves
04/29/2024, 9:54 AMJoão Horta Alves
04/29/2024, 9:55 AMJoão Horta Alves
04/29/2024, 9:58 AMsimon.vergauwen
04/29/2024, 10:06 AMJoão Horta Alves
04/29/2024, 10:09 AMsimon.vergauwen
04/29/2024, 10:10 AMsimon.vergauwen
04/29/2024, 10:13 AMJoão Horta Alves
04/29/2024, 10:16 AM@file:UseSerializers
, at least in Ktorsimon.vergauwen
04/29/2024, 10:18 AM@file:UserSerializers
is used inside the generated code, but serializers module affects runtime lookup.simon.vergauwen
04/29/2024, 10:18 AM@file:UserSerializers
is required for example for val instant: org.jebtrains.kotlinx.datetime.Instant
.João Horta Alves
04/29/2024, 10:24 AMSerializer has not been found for type 'NonEmptyList<String>'.
because the SerializersModule
mechanism must be only runtimesimon.vergauwen
04/29/2024, 10:26 AM@file:UseSerializers(NonEmptyListSerializer::class)
which the compiler will pick-up while generating the serializer for the data classJoão Horta Alves
04/29/2024, 10:30 AMGarth Gilmour
04/29/2024, 10:37 AMphldavies
04/29/2024, 11:45 AMval ArrowSerializersModule = SerializersModule {
contextual(Either::class) { (a, b) -> EitherSerializer(a, b) }
contextual(Ior::class) { (a, b) -> IorSerializer(a, b) }
contextual(NonEmptyList::class) { (t) -> NonEmptyListSerializer(t) }
contextual(NonEmptySet::class) { (t) -> NonEmptySetSerializer(t) }
// contextual(Option::class) { (t) -> OptionSerializer(t) } // mismatch on T vs T: Any
@Suppress("DEPRECATION")
contextual(Validated::class) { (a, b) -> ValidatedSerializer(a, b) }
}
allowing consumers to just use
install(ContentNegotiation) {
json(Json {
serializersModule = SerializersModule {
include(ArrowSerializersModule)
}
})
}
?simon.vergauwen
04/29/2024, 11:46 AMphldavies
04/29/2024, 11:49 AMOption<T>
vs OptionSerializer<T: Any>
discrepancysimon.vergauwen
04/29/2024, 12:06 PMOption
for now in the PR and we'll while go.phldavies
04/29/2024, 1:22 PMOptionSerializer
being declared as <T: Any>
is ignored by kotlinx-serialization.
@Serializable
data class MyData(val a: @Serializable(with=OptionSerializer::class) Option<String?>)
class Example {
@Test fun example() {
val json = Json.encodeToJsonElement(MyData(Some(null)))
println(json)
Json.decodeFromJsonElement<MyData>(json) shouldBe MyData(Some(null))
}
}
This compiles fine, despite the discrepancy but the current implementation of OptionSerializer
doesn't pass this test (I kinda don't expect it to either as JSON has no nested-nullability)
It seems the OptionSerializer
only has <T: Any>
to allow it to access the KSerializer<T: Any>.nullable
property, but we can work around this with the following:
@Suppress("UNCHECKED_CAST")
@OptIn(ExperimentalSerializationApi::class)
private val <T> KSerializer<T>.nullable get() =
if (descriptor.isNullable) (this as KSerializer<T?>)
else (this as KSerializer<T & Any>).nullable
Which means we can lift the restriction on OptionSerializer<T>
and use it in contextual
, given as the restriction is ignored anyway. The only real impact of the restriction is not being able to use OptionSerializer(String.serializer().nullable)
phldavies
04/29/2024, 2:58 PM