Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.4k stars 621 forks source link

Allow meta-annotations with Serializable for elements #2741

Open Chuckame opened 3 months ago

Chuckame commented 3 months ago

What is your use-case and why do you need this feature? Currently, annotating a property with an annotation having MetaSerializable instructs the serialization plugin to treat the annotated data class as serializable.

Doing that onto a field does't instruct the serialization plugin, as it misses the final serializer to be used: contextual ? custom ? something else ?

@MetaSerializable
@SerialInfo
annotation class MyAnnotation(val data: String)

@MyAnnotation("some content") // <-- the data class is treated as Serializable and is accessible from the `SerialDescriptor` annotations
data class MyData(
    @MyAnnotation("some content") // <-- ERROR: Serializer has not been found for type 'MyType' as the serializer needs to be explicit or contextual
    val theField: MyType,
)

Describe the solution you'd like Allow @Serializable to be put on a meta-annotation, allowing us to unify the serializers with their possible configuration as we cannot pass params because of we need to provide a class.

I'm giving you an example: an avro fixed type requires some details to work properly, like its size in bytes. To configure the serializer to encode the content in 10 bytes, we can do like the following:

@Serializable
data class MyData(
    @Serializable(with = ByteArrayFixedSerializer::class)
    @AvroFixed(size = 10)
    val theField: ByteArray,
)

Or even, more generically, for a given BigDecimal where we allow a maximum precision when encoding to a String:

@Serializable
data class MyData(
    @Serializable(with = BigDecimalStringSerializer::class)
    @DecimalPrecision(digits = 3)
    val theField: BigDecimal,
    @Serializable(with = AnotherDecimalSerializer::class)
    @DecimalPrecision(digits = 3) // <--- This is useless as not handled by the AnotherDecimalSerializer implementation
    val theField2: BigDecimal,
)

At the end, here would be the final code, making it clearer for the users IMO as it reduces the noise of needing many annotations:

@SerialInfo
@MetaSerializable
@Serializable(with = ByteArrayFixedSerializer::class)
annotation class AvroFixed(val size: Int)

@SerialInfo
@MetaSerializable
@Serializable(with = BigDecimalStringSerializer::class)
annotation class DecimalPrecision(val digits: Int)

@Serializable
data class MyData(
    @AvroFixed(size = 10)
    val theBytesField: ByteArray,
    @DecimalPrecision(digits = 3)
    val theDecimalField: BigDecimal,

    @AvroFixed(size = 10)
    @DecimalPrecision(digits = 3)
    val wrongField: BigDecimal,// <--- ERROR: Doesn't compile as we are trying to apply 2 different serializers

    @DecimalPrecision(digits = 3)
    @Serializable(with = AnotherBigDecimalSerializer::class)
    val example1: BigDecimal,// <--- AnotherBigDecimalSerializer takes precedence

    @AvroFixed(size = 10)
    val example1: BigDecimal,// <--- ERROR: Same as explicit `Serializable` annotation, where we are not able to annotate with a serializer of a different type
)
sandwwraith commented 3 weeks ago

That's a great feature, but there is one big piece missing: passing property info to custom serializers. Currently, ByteArrayFixedSerializer would have no access to MyData.descriptor to actually understand that wrongField has AvroFixed with size = 10, even if we apply it automatically — because all info about properties is stored in the outer descriptor. So this feature requires at least designing passing serial info to custom serializers, e.g. in a form of adding Array<Annotation> to a custom serializer constructor.