Reputation: 101
With kotlinx.serialization, this code will throw an error:
println(Json.encodeToString(Path.of("value")))
saying kotlinx.serialization.SerializationException: Class 'WindowsPath' is not registered for polymorphic serialization in the scope of 'Path'.
WindowsPath is internal, therefore I can not register it as a polymorphic subclass (as in this example), only with Path itself, and even a custom KSerializer for Path throws the same exact error.
Is there any way to have Path properly serialize/deserialize without having to store it as a string?
Upvotes: 5
Views: 2538
Reputation: 7882
Path
is an interface, so it's implicitly serializable with the PolymorphicSerializer strategy. This strategy requires you to register serializers for subclasses implementing it, but as you know, in this case it's impossible.
There is a default polymorphic serializer, but it affects only deserialization process and it works only when deserializable value is a JSONObject.
And for the following serializer
object PathAsStringSerializer : KSerializer<Path> {
override val descriptor = PrimitiveSerialDescriptor("Path", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Path) = encoder.encodeString(value.toAbsolutePath().toString())
override fun deserialize(decoder: Decoder): Path = Path.of(decoder.decodeString())
}
\\Not working
val module = SerializersModule { polymorphicDefault(Path::class) { PathAsStringSerializer } }
val decoded : Path = Json { serializersModule = module }.decodeFromString("C:\\Temp")
it will throw runtime exception kotlinx.serialization.json.internal.JsonDecodingException: Expected class kotlinx.serialization.json.JsonObject as the serialized body of kotlinx.serialization.Polymorphic<Path>, but had class kotlinx.serialization.json.JsonLiteral
So, it can't be serialized in a common way, and there are 3 cases of its serialization/deserialization, which need to be handled:
Path
variableIn this case you need to explicitly pass your custom serializer:
val path = Path.of("C:\\Temp")
val message1 = Json.encodeToString(PathAsStringSerializer, path).also { println(it) }
println(Json.decodeFromString(PathAsStringSerializer, message1))
Path
as a generic parameterIn this case you need to define separate serializers (you may reference original PathAsStringSerializer
) and also explicitly pass them:
object ListOfPathsAsStringSerializer : KSerializer<List<Path>> by ListSerializer(PathAsStringSerializer)
val message2 = Json.encodeToString(ListOfPathsAsStringSerializer, listOf(path)).also { println(it) }
println(Json.decodeFromString(ListOfPathsAsStringSerializer, message2))
@Serializable
data class Box<T>(val item: T)
object BoxOfPathSerializer : KSerializer<Box<Path>> by Box.serializer(PathAsStringSerializer)
val message3 = Json.encodeToString(BoxOfPathSerializer, Box(path)).also { println(it) }
println(Json.decodeFromString(BoxOfPathSerializer, message3))
In this case you need to add respectful @Serializable(with = ...)
annotations for these fields:
@Serializable
data class InnerObject(
@Serializable(with = ListOfPathsAsStringSerializer::class)
val list: MutableList<Path> = mutableListOf(),
@Serializable(with = PathAsStringSerializer::class)
val path: Path,
@Serializable(with = BoxOfPathSerializer::class)
val box: Box<Path>
)
Or just list them once for a whole file:
@file: UseSerializers(PathAsStringSerializer::class, ListOfPathsAsStringSerializer::class, BoxOfPathSerializer::class)
Plugin-generated serializer for this case would be good enough:
val message4 = Json.encodeToString(InnerObject(mutableListOf(path), path, Box(path))).also { println(it) }
println(Json.decodeFromString<InnerObject>(message4))
Upvotes: 2