Hi, is there a way to write generic serializers fo...
# serialization
s
Hi, is there a way to write generic serializers for JVM classes that do nothing but use
toString()
? Currently, I'm writing code like
Copy code
@Serializer(File::class)
object FileSerializer : KSerializer<File> {
    override fun serialize(encoder: Encoder, value: File) = encoder.encodeString(value.toString())
    override fun deserialize(decoder: Decoder) = File(decoder.decodeString())
}

@Serializer(URI::class)
object URISerializer : KSerializer<URI> {
    override fun serialize(encoder: Encoder, value: URI) = encoder.encodeString(value.toString())
    override fun deserialize(decoder: Decoder) = URI(decoder.decodeString())
}
but this duplication seems rather dumb. Can I somehow generalize these serializers into one, and use that single serializer for both
File
and
URI
?
p
You’ll need to capture the constructor call either as a function parameter or as an abstract method. Something like this might work (completely untested):
Copy code
class ToStringSerializer<T: Any>(private val ctor: (String) -> T): KSerializer<T> {
    override fun serialize(encoder: Encoder, value: T) = encoder.encodeString(value.toString())
    override fun deserialize(decoder: Decoder) = ctor(decoder.decodeString())
}

@Serializer(File::class)
object FileSerializer: ToStringSerializer(::File)

@Serializer(Uri::class)
object URISerializer: ToStringSerializer(::URI)
s
Thanks, I'll give that a try. But I guess it's not possible to use exactly the same serializer class for multiple classes. as the registration is hard-wired and does not allow to pass some sort of generic type argument, right?
p
If you only wanted to serialize using toString (no deserializing) then you can probably get away with a single instance/serializer (with the deserialize method just being
= TODO()
or something), and use an annotation on each property you want to use it on.
e
Copy code
abstract class ByString<T>(serialName: String) {
    abstract fun parse(string: String): T
    override val serialDescriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.STRING)
    override fun serialize(encoder: Encoder, value: T) {
        encoder.encodeString(value.toString())
    }
    override fun deserialize(decoder: Decoder): T = parse(decoder.decodeString())
}

object FileSerializer : ByString<File>("java.io.File") {
    override fun parse(string: String): File = File(string)
}

object URISerializer : ByString<URI>("java.net.URI") {
    override fun parse(string: String): URI = URI(string)
}
but yeah you can't use the exact same serializer for all types, it has to know what type to construct
p
I always forget the serialDescriptor 😉 (and my IDE was updating, so no code assist for me!)
s
Can't I get rid of the need to add `serialDescriptor`manually by annotating the serializer with
@Serializer
?
... ah, I see, then I would need the type in the annotation.
e
also you need to tell it it's a primitive string kind - not too important for JSON but definitely important for other formats
s
Thanks guys, I'm no going with
Copy code
open class ToStringSerializer<T: Any>(serialName: String, private val ctor: (String) -> T): KSerializer<T> {
    override val descriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.STRING)
    override fun serialize(encoder: Encoder, value: T) = encoder.encodeString(value.toString())
    override fun deserialize(decoder: Decoder) = ctor(decoder.decodeString())
}

object FileSerializer : ToStringSerializer<File>(File::class.qualifiedName!!, ::File)

object URISerializer : ToStringSerializer<URI>(URI::class.qualifiedName!!, ::URI)
Would be nice to get rid of the type redundancy in the objects, but I guess I'd need reified generics on the class level for that...
p
You could make it work with delegation and an inline reified function with the same name
Copy code
inline fun <reified T : Any> ToStringSerializer(noinline ctor: (String) -> T): ToStringSerializer<T> {
    return ToStringSerializer(T::class.java.simpleName, ctor)
}

object FileSerializer : KSerializer<File> by ToStringSerializer(::File)
👍🏻 1