1. 概述

在 Kotlin 开发中,JSON 序列化是常见需求,而枚举(enum)作为类型安全的常量集合,经常需要参与序列化过程。默认情况下,枚举会被序列化为其名称(name),但实际开发中我们往往希望使用枚举的某个属性值(如 description)作为 JSON 输出内容。

本文将介绍如何使用主流的 Kotlin JSON 序列化库(Jackson、Gson、kotlinx.serialization)实现 将枚举的属性值而非名称 序列化为 JSON 字符串。

✅ 目标:Language.KOTLIN"Kotlin_is_awesome" 而非 "KOTLIN"


2. 问题背景

先看一个典型场景。假设我们有如下表示编程语言的枚举:

enum class Language(val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}

再定义一个使用该枚举的数据类:

data class Dev(val name: String, val language: Language)

当我们尝试将其序列化为 JSON 时(以 Jackson 为例):

val mapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
val dev = Dev("Kai", Language.KOTLIN)
val json = mapper.writeValueAsString(dev)
assertEquals("""{"name":"Kai","language":"KOTLIN"}""", json)

⚠️ 结果显示:默认行为是使用枚举的 name 字段进行序列化,即 KOTLIN

这显然不符合我们的预期——我们想要的是 description 的值 "Kotlin_is_awesome"

这个问题在 Gson 和 kotlinx.serialization 中同样存在,属于通用痛点。下面分别介绍三种主流方案的解决方式。


3. 使用 Jackson 库

Jackson 提供了 @JsonValue 注解,用于指定哪个方法或属性的返回值应作为该对象的 JSON 表示。

✅ 解决方案:将 @JsonValue 标注在目标属性上即可。

enum class Language(@JsonValue val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}
 
data class Dev(val name: String, val language: Language)

测试验证:

val mapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
val dev = Dev("Kai", Language.KOTLIN)
val json = mapper.writeValueAsString(dev)
assertEquals("""{"name":"Kai","language":"Kotlin_is_awesome"}""", json)

✅ 成功!description 值被正确输出。

💡 小贴士:@JsonValue 只能标注在一个成员上,且通常用于“可直接转换为基本类型的字段”,非常适合枚举的字符串映射。


4. 使用 Gson 库

Gson 提供两种方式来自定义枚举序列化行为:

4.1 使用 @SerializedName 注解

通过 @SerializedName 显式指定每个枚举实例在 JSON 中的输出名称。

enum class Language(val description: String) {
    @SerializedName("Kotlin_is_awesome")
    KOTLIN("Kotlin_is_awesome"),
 
    @SerializedName("Java_is_great")
    JAVA("Java_is_great"),
 
    @SerializedName("Go_is_nice")
    GO("Go_is_nice")
}
 
data class Dev(val name: String, val language: Language)

序列化测试:

val dev = Dev("Kai", Language.GO)
val json = Gson().toJson(dev)
assertEquals("""{"name":"Kai","language":"Go_is_nice"}""", json)

✅ 功能可用,但 ⚠️ 存在明显缺点:

  • 需要手动复制每个 description 值到 @SerializedName
  • 枚举项多时易出错、难维护
  • 不支持动态值(无法写成 @SerializedName(description)

❌ 属于“静态硬编码”,适合固定别名场景,不适合动态属性映射。

4.2 创建自定义 Serializer

更灵活的方式是实现 JsonSerializer<T> 接口,完全控制序列化逻辑。

class LanguageSerializer : JsonSerializer<Language> {
    override fun serialize(
        src: Language?, 
        typeOfSrc: Type?, 
        context: JsonSerializationContext?
    ): JsonElement {
        return JsonPrimitive(requireNotNull(src).description)
    }
}

保持枚举原样不变:

enum class Language(val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}
 
data class Dev(val name: String, val language: Language)

注册并使用自定义序列化器:

val gson = GsonBuilder()
    .registerTypeAdapter(Language::class.java, LanguageSerializer())
    .create()

val dev = Dev("Kai", Language.GO)
val json = gson.toJson(dev)
assertEquals("""{"name":"Kai","language":"Go_is_nice"}""", json)

✅ 优点:

  • 灵活,支持任意复杂逻辑
  • 枚举代码干净无侵入
  • 易于复用和测试

💡 推荐用于需要统一处理或逻辑复杂的场景。


5. 使用 kotlinx.serialization

作为 Kotlin 官方推荐的序列化框架,kotlinx.serialization 同样支持两种方式。

5.1 使用 @SerialName 注解

与 Gson 类似,可通过 @SerialName 指定每个枚举实例的序列化名称。

注意:需添加 @Serializable 注解开启序列化支持。

@Serializable
enum class Language(val description: String) {
    @SerialName("Kotlin_is_awesome")
    KOTLIN("Kotlin_is_awesome"),
 
    @SerialName("Java_is_great")
    JAVA("Java_is_great"),
 
    @SerialName("Go_is_nice")
    GO("Go_is_nice")
}
 
@Serializable
data class Dev(val name: String, val language: Language)

序列化验证:

val dev = Dev("Kai", Language.JAVA)
val json = Json.encodeToString(dev)
assertEquals("""{"name":"Kai","language":"Java_is_great"}""", json)

✅ 成功,但同样面临 重复赋值、难以维护 的问题。

⚠️ 不适用于描述值动态生成或频繁变更的场景。

5.2 创建自定义 Serializer

继承 KSerializer<T> 实现完全控制权。

class LanguageSerializer : KSerializer<Language> {
    override val descriptor: SerialDescriptor = 
        PrimitiveSerialDescriptor("Language", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): Language {
        val desc = decoder.decodeString()
        return Language.entries.first { it.description == desc }
    }

    override fun serialize(encoder: Encoder, value: Language) {
        encoder.encodeString(value.description)
    }
}

绑定到枚举类:

@Serializable(with = LanguageSerializer::class)
enum class Language(val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}

@Serializable
data class Dev(val name: String, val language: Language)

测试:

val dev = Dev("Kai", Language.JAVA)
val json = Json.encodeToString(dev)
assertEquals("""{"name":"Kai","language":"Java_is_great"}""", json)

✅ 成功,且逻辑集中、可扩展性强。

💡 提示:反序列化时注意处理非法输入(如找不到匹配项),生产环境建议加异常兜底。


6. 总结

方案 是否推荐 适用场景
Jackson @JsonValue ✅ 强烈推荐 简单直接,一行注解搞定
Gson @SerializedName ⚠️ 有限使用 枚举项少且名称固定的场景
Gson 自定义 JsonSerializer ✅ 推荐 复杂逻辑或需统一管理
kotlinx.serialization @SerialName ⚠️ 有限使用 同 Gson 的 @SerializedName
kotlinx.serialization 自定义 KSerializer ✅ 推荐 Kotlin 原生项目首选

📌 最佳实践建议

  • 若使用 Jackson,优先用 @JsonValue
  • 若使用 Gson 或 kotlinx.serialization,优先选择 自定义序列化器,避免重复代码
  • 枚举设计时考虑是否真的需要携带额外属性,避免过度设计

所有示例代码已托管至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/kotlin-json-2


原始标题:Serialize Enum Property to JSON in Kotlin