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