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

Enhance enum serialization with nullability, @SerialName support, caching and informative logging. #183

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

karloti
Copy link

@karloti karloti commented Jun 14, 2024

Introduce enumSerializer to provide type-safe, efficient enum serialization with support for nullable enums, custom enum names using @SerialName, and optimized lookups through caching. Deprecate FirstOrdinalSerializer, which lacked these features and could lead to issues with evolving backend APIs.

The new approach gracefully handles unknown enum values by returning null and logging a message, encouraging developers to update the SDK or report the issue. Caching enum lookups ensures O(1) complexity for serialization and deserialization, significantly improving performance.

enum class Country and data class Person

object CountrySerializer : KSerializer<Country?> by enumSerializer()

@Serializable(with = CountrySerializer::class)
enum class Country {
    @SerialName("UNITED_STATES_OF_AMERICA")
    USA,
    OTHER,
}

@Serializable
data class Person(
    val name: String,
    val nationality: Country?,
    val residence: Country?
)
val person = Person("William", USA, null)

val json = """{"name":"William","nationality":"UNITED_STATES_OF_AMERICA","residence":null}"""
require(json == Json.encodeToString(person))
require(person == Json.decodeFromString<Person>(json))

Unknown enum value found: "FRANCE" in Country
This usually means the backend was updated, and the SDK needs to be updated to match it.
Check if there's a new version for the SDK, otherwise please open an issue on our
GitHub to bring it to our attention:
https://github.com/google/google-ai-android

val jsonMissing = """{"name":"William","nationality":"FRANCE","residence":"OTHER"}"""
val personMissing = Person(name = "William", nationality = null, residence = OTHER)
require(personMissing == Json.decodeFromString<Person>(jsonMissing))

tests

extension function from caches. HashMap implementation: O(1)
require(USA.serialName == "UNITED_STATES_OF_AMERICA")
require("USA".enumByName() == USA)
require("USA".enumBySerialName() == null)
require("OTHER".enumByName() == OTHER)
require("OTHER".enumBySerialName() == null)
require("UNITED_STATES_OF_AMERICA".enumByName() == null)
require("UNITED_STATES_OF_AMERICA".enumBySerialName() == USA)
require("MISSING".enumByName() == null)
require("MISSING".enumBySerialName() == null)

serialize with @SerialName
require(Json.encodeToString(USA) == ""UNITED_STATES_OF_AMERICA"")

serialize without @SerialName
require(Json.encodeToString(OTHER) == ""OTHER"")

deserialize with @SerialName or enumName
require(Json.decodeFromString<Country?>(""UNITED_STATES_OF_AMERICA"") == USA)
require(Json.decodeFromString<Country?>(""USA"") == USA)
require(Json.decodeFromString<Country?>(""OTHER"") == OTHER)
require(Json.decodeFromString<Country?>("null") == null)

deserialize missing enum with warning message
Unknown enum value found: "MISSING" in Country
This usually means the backend was updated, and the SDK needs to be updated to match it.
Check if there's a new version for the SDK, otherwise please open an issue on our
GitHub to bring it to our attention:
https://github.com/google/google-ai-android

require(Json.decodeFromString<Country?>(""MISSING"") == null)

A generic serializer for enum classes using Kotlin Serialization with caches.
This serializer handles the serialization and deserialization of enum values as strings,
using either the serialName (if available) or the regular name of the enum.
@param T The enum type to serialize.

image

A utility object that provides caching for enum name and serialized name lookups.
This object maintains three caches:*

  • serialNameByEnum: Maps enum instances to their serialized names (as defined by the @SerialName annotation).
  • enumByEnumName: Maps enum names to their corresponding enum instances.
  • enumBySerialName: Maps serialized names to their corresponding enum instances.
    The caches are populated lazily, meaning that the mappings are generated only when a particular enum class is accessed for the first time.

image

functions with cache
image

Introduce enumSerializer function toprovide a more type-safe and concise way to serialize enums.
 This new approach supports nullable enums (EnumClass?) and eliminates the need for custom enum serializers.

Deprecate FirstOrdinalSerializer in favor of enumSerializer, as it offers better type safety and nullability handling.
 Update usage to reflect the newrecommended approach.

This change improves the robustness and maintainability of enum serialization within the project.
Copy link

google-cla bot commented Jun 14, 2024

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@karloti
Copy link
Author

karloti commented Jun 21, 2024

In my solution, if Gemini returns UNKNOWN it will be perfectly normal. I often use an ENUM that is UNKNOWN. I recommend not hacking with an ENUM that is set to the first position. LOGGER will print the non-existent serialization field and it will take the value NULL in the array.

image

@karloti
Copy link
Author

karloti commented Jun 21, 2024

In your solution, if Gemini returns UNKNOWN you won't know if this is wrong. I often use an ENUM that is UNKNOWN. I recommend not hacking with an ENUM that is set to the first position. LOGGER will print the non-existent serialization field and it will take the value NULL in the array.

image

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

Successfully merging this pull request may close these issues.

1 participant