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

Annotation for applying @Serializable to nested classes #2640

Open
mgroth0 opened this issue Apr 18, 2024 · 0 comments
Open

Annotation for applying @Serializable to nested classes #2640

mgroth0 opened this issue Apr 18, 2024 · 0 comments
Labels

Comments

@mgroth0
Copy link

mgroth0 commented Apr 18, 2024

If I want to define a class hierarchy of small serializable classes, the @Serializable annotation can become painfully redundant. This can make the code at worst twice as long, and reduces the readability significantly.

Before writing this, I considered some previous requests. These requests were similar, but none quite captured the idea this way, so I thought it would be worth it to create a new issue.

The important details for my proposal are:

  1. If our computer screen is capable of fitting, say, 10 class definitions on it, it would be good to just see a single annotation saying "all of these classes are serializable"
  2. This proposal does not change the current behavior of @Serializable, it is backwards compatible.
  3. This proposal is for a new annotation (or possible new target site of @Serializable). It simple reads as "all of these classes you see here will have generated serializers".
  4. This annotation, in its simplest form, does not need to have any arguments.

Here is some code the exemplifies the main issue. This code is made twice as long because of all of the Serializable annotations.

@Serializable
class Zoo(val animals: Set<Animal>)
@Serializable
sealed interface Animal
@Serializable
data class Penguin(val name: String, val age: Int, val isHappyFeet: Boolean): Animal
@Serializable
data class Lion(val name: String, val maneLength: Int): Animal
@Serializable
data class Elephant(val name: String, val trunkLength: Int, val weight: Int): Animal
@Serializable
data class Monkey(val name: String, val favoriteFood: String): Animal
@Serializable
data class Giraffe(val name: String, val neckLength: Int): Animal
@Serializable
data class Tiger(val name: String, val stripeCount: Int): Animal
@Serializable
data class Zebra(val name: String, val stripePattern: String): Animal
@Serializable
data class Parrot(val name: String, val canTalk: Boolean): Animal
@Serializable
data class Snake(val name: String, val length: Int): Animal
@Serializable
data class Bear(val name: String, val type: String): Animal

This propsal is for an annotation such as:

@AllSerializable
object ZooClasses {
    class Zoo(val animals: Set<Animal>)
    sealed interface Animal
    data class Penguin(val name: String, val age: Int, val isHappyFeet: Boolean): Animal
    data class Lion(val name: String, val maneLength: Int): Animal
    data class Elephant(val name: String, val trunkLength: Int, val weight: Int): Animal
    data class Monkey(val name: String, val favoriteFood: String): Animal
    data class Giraffe(val name: String, val neckLength: Int): Animal
    data class Tiger(val name: String, val stripeCount: Int): Animal
    data class Zebra(val name: String, val stripePattern: String): Animal
    data class Parrot(val name: String, val canTalk: Boolean): Animal
    data class Snake(val name: String, val length: Int): Animal
    data class Bear(val name: String, val type: String): Animal
}

My idea is that ZooClasses itself is not made serializable from @AllSerializable, but that detail can be changed.

One workaround to reduce the number of lines is to put @Serializable on the same line as the definition like:

@Serializable data class Bear(val name: String, val type: String): Animal

However, has a couple issues:

  1. It will usually break formatter rules, so these formatter rules would have to be suppressed on the file
  2. It adds significant width to each line, makes them less readable, and reduces how many properties can fit on one line

For my example, I used single-line class definitions to highlight the issue. However, even for more typical class definitons that are multiple lines, this issue still matters.

I think this might be something that is possible to implement without many issues. When the compiler plugin sees @AllSerializable, it behaves exactly as if each nested class had @Serializable.

If a class inside ZooClasses has @Serializable with no arguments, the behavior is the same. There could be a warning inidicating the redundancy.

If a class inside ZooClasses has @Serializable with a custom serializer, the custom serializer is generated like normal, and the plugin does not do anything special (it doesn't generate two serializers)

This would also work with objects and enums, following the same logic.

There are no special rules or requirements regarding sealed classes/interfaces. Using AllSerializable would behave exactly like putting Serializable on each individual class or object inside the group.

Whether a class is inner or not doesn't matter. The only thing that matters is class nesting.

@file:Serializable or @file:AllSerializable could possibly be implemented as part of this. However, @file annotations are easy to miss since they are above the import statements. I think including @file annotations could make this request more controversial, so I propose not to include them (at least for now).

Here is how this request is different from similar previous requests:

This request was for plugin-wide automatic serializers. It was closed without fixing based on conerns about readability, security, and performance and I think none of the issues mentioned there apply here

This request was closed for the same reasons as 1808, which I think are resolved here

This request is for applying @Serializable based on sealed class hierarchy. Although the motivations are overlapping, the implementation in my proposal is different. My proposal is about applying an annotation based on being close together in the file and nothing to do with class hierarchies. I think that all of the issues mentioned by @sandwwraith in his comment for that issue are addressed and solved in my proposal:

it would require the plugin to analyze every single class in the project and its superclasses

This will not be an issue here, because this feature is based on an annotation. The compiler plugin will have to handle each annotation by checking nested classes, but I think that seems very small compared to scanning the whole module.

if my intention to have only some of the inheritors serializable, how can I opt-out?

Since this is just based on being a nested class in the annotated group, opting-out is as simple as moving the class you don't want to serialize outside of the class or object annotated with @AllSerializable.

it would not be backward-compatible

This proposal should be backwards compatible

Note also that @sandwwraith mentioned the idea of

@SerializableSubclasses for nested classes or classes in the same file

in that comment. I wasn't sure exactly what that meant, but to be clear this proposal is for an annotation that doesn't care whatsover about subclasses. I think that would help make this annotation be more simple and readable.

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

No branches or pull requests

1 participant