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

Option to serialize enums as lowercase/snake_case #2762

Open
stijndcl opened this issue Aug 6, 2024 · 4 comments
Open

Option to serialize enums as lowercase/snake_case #2762

stijndcl opened this issue Aug 6, 2024 · 4 comments
Labels

Comments

@stijndcl
Copy link

stijndcl commented Aug 6, 2024

What is your use-case

PR #2345 added an option to deserialize enum values in a case-insensitive manner. However, this only solves the problem in one direction. In an application that interacts with an external API, it is not unrealistic to assume that these enums may be sent to the server as well.

The current solution is adding a @SerialName() annotation to every single enum value. This is not only incredibly tedious for large enums and/or large amounts of enums, but also defeats the point of the decodeEnumsCaseInsensitive option as the serial name is now hard coded everywhere (apart from API's that respond by switching cases arbitrarily, which should not realistically occur).

For regular properties this is solved entirely using JsonNamingStrategy, but this does not apply to enum values for some reason. This option was added for properties for a good reason (very common use-case, too tedious to do manually everywhere), and I'd argue the exact same reasoning applies to enum values.

Why do you need this feature?

I am currently writing a wrapper for a large API with a lot of enums in snake_case, and plenty of endpoints allow receiving these enums in request bodies as well. I don't want to add 500 @SerialName annotations myself. We got a solution for properties, why not for enums?

Describe the solution you'd like

  • Solution 1: Apply JsonNamingStrategy to enum values as well
  • Solution 2: Add an equivalent to JsonNamingStrategy to turn all enum values from whatever the user defined them as into snake_case, SCREAMING_SNAKE_CASE and CamelCase. This should preferably respect existing @SerialName annotations and use the defined value if present, to allow for edge cases to be fixed manually.

It's currently possible to implement solution 2 for properties using a JsonNamingStrategy as follows:

@OptIn(ExperimentalSerializationApi::class)
@SerialInfo
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class ForcedSerialName

fun List<Annotation>.hasExplicitSerialName(): Boolean = this.filterIsInstance<ForcedSerialName>().isNotEmpty()

// Custom naming strategy that respects explicit @SerialName annotations
@OptIn(ExperimentalSerializationApi::class)
private val jsonNamingStrategy = JsonNamingStrategy { descriptor, elementIndex, serialName ->
    if (descriptor.annotations.hasExplicitSerialName() || descriptor.getElementAnnotations(elementIndex).hasExplicitSerialName()) {
        serialName
    } else {
        JsonNamingStrategy.SnakeCase.serialNameForJson(descriptor, elementIndex, serialName)
    }
}

However, this is not possible for enums. The only realistic solution that automates some of the work is implementing a custom serializer and deserializer for every single enum you have, just like we had to do for regular class properties before JsonNamingStrategy.SnakeCase.

@stijndcl stijndcl changed the title Option to serialize enums lowercase Option to serialize enums as lowercase/snake_case Aug 6, 2024
@sandwwraith
Copy link
Member

Adding an option for JsonNamingStrategy to handle enums is something that was initially considered but then discarded: #2111 (comment)

In an application that interacts with an external API, it is not unrealistic to assume that these enums may be sent to the server as well.

This is a perfectly valid assumption. However, it is also fine to assume that if your server sends foo_bar that you wish to deserialize to FOO_BAR, then this server may accept FOO_BAR as well. In other words, if both sides of the conversation have decodeEnumsCaseInsensetive enabled, then you do not need a naming strategy at all.

This should preferably respect existing @SerialName annotations and use the defined value if present, to allow for edge cases to be fixed manually.

Unfortunately it is something that we cannot do, since @SerialNames are baking in by plugin during the compilation.

I've also searched in the tracker, and naming strategy for enums is not a very popular request. From the looks of it, your problem can be solved with something like lowerCaseEnumsOnSerialization rather than a full-blown strategy. Is that true?

@stijndcl
Copy link
Author

stijndcl commented Aug 7, 2024

In other words, if both sides of the conversation have decodeEnumsCaseInsensetive enabled, then you do not need a naming strategy at all.

Keep in mind that not every API out there is written in Kotlin and uses this library. In my case I am using an external API, out of my control, written in whatever framework they decided on. In an ideal world both sides have this enabled, but in practice this is not necessarily the case.

The only reason I specifically want to deserialize this to FOO_BAR instead of foo_bar is because of kotlin naming conventions. A lowercase-snake case enum value looks very off. Screaming snake case is easily acceptable.

From the looks of it, your problem can be solved with something like lowerCaseEnumsOnSerialization

That sounds like what I'm looking for, and if you adopt a SCREAMING_SNAKE_CASE naming convention for enum values it basically serves as a simpler snake case namingStrategy.

Does this exist, or is this a proposal? I remember looking through the docs before making this issue and not finding an option like that. I just checked again and I still can't find it. Could you point me to where it is?

If it does not yet exist, please keep SerialName annotations in mind for edge cases as I mentioned in the issue. If a user explicitly adds a SerialName annotation it is a safe assumption that they will just add the correctly cased one immediately.

@sandwwraith
Copy link
Member

Does this exist, or is this a proposal? I remember looking through the docs before making this issue and not finding an option like that. I just checked again and I still can't find it. Could you point me to where it is?

No, it doesn't exist, I was theoretical about this option. As I said, serial names completely replace properties' names, so one has to be careful there

@stijndcl
Copy link
Author

stijndcl commented Aug 7, 2024

In that case yes that option would solve my issues. I suppose the serial name issue will usually be rare enough that I could just write a custom KSerializer for these enum classes and handle them the verbose way.

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

2 participants