1. 简介
在实际开发中,经常会遇到需要将数据反序列化为枚举(enum)类型的情况。但数据源中可能包含不在我们定义的枚举值中的字段,这会导致运行时异常。
本文将介绍在 Kotlin 中如何安全地处理枚举反序列化,并忽略未知值。我们将分别使用三个常用的库来演示:kotlinx.serialization、Jackson 和 Gson。
2. 使用 kotlinx.serialization
kotlinx.serialization 是 Kotlin 官方推荐的序列化库,功能强大且灵活,支持通过自定义序列化器处理各种复杂场景。
首先定义一个简单的枚举类:
enum class Status {
SUCCESS,
ERROR,
UNKNOWN
}
接下来,我们为 Status
枚举创建一个自定义的反序列化器:
object StatusSerializer : KSerializer<Status> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Status", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Status) {
encoder.encodeString(value.name)
}
override fun deserialize(decoder: Decoder): Status {
return try {
Status.valueOf(decoder.decodeString().uppercase())
} catch (e: IllegalArgumentException) {
Status.UNKNOWN
}
}
}
✅ 关键点:在
deserialize
方法中,如果字符串无法匹配枚举值,则返回Status.UNKNOWN
,避免抛出异常。
接着定义一个数据类并使用该序列化器:
@Serializable
data class ApiResponse(
@Serializable(with = StatusSerializer::class)
val status: Status
)
测试反序列化已知值:
@Test
fun `test known status value deserialization`() {
val json = """{"status": "success"}"""
val response = Json.decodeFromString(json)
assertEquals(Status.SUCCESS, response.status)
}
测试反序列化未知值:
@Test
fun `test unknown status value deserialization`() {
val unknownJson = """{"status": "unknown_value"}"""
val unknownResponse = Json.decodeFromString(unknownJson)
assertEquals(Status.UNKNOWN, unknownResponse.status)
}
3. 使用 Jackson
Jackson 是 Java/Kotlin 社区中广泛使用的 JSON 处理库,也支持优雅地处理未知枚举值。
我们可以使用 @JsonEnumDefaultValue
注解来标记默认值:
enum class Status {
SUCCESS,
ERROR,
@JsonEnumDefaultValue
UNKNOWN
}
✅ 注意:这个注解是 Jackson 提供的,必须配合特定配置使用。
配置 ObjectMapper
以启用默认值处理:
val mapper = jacksonObjectMapper().apply {
configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true)
}
测试已知值:
@Test
fun `test known status value deserialization`() {
val json = """{"status": "SUCCESS"}"""
val response = mapper.readValue(json)
assertEquals(Status.SUCCESS, response.status)
}
测试未知值:
@Test
fun `test unknown status value deserialization`() {
val unknownJson = """{"status": "unknown_value"}"""
val response = mapper.readValue(unknownJson)
assertEquals(Status.UNKNOWN, response.status)
}
4. 使用 Gson
Gson 是另一个流行的 JSON 序列化/反序列化库。虽然它不像 Jackson 那样内置支持默认枚举值,但可以通过自定义反序列化器实现。
创建自定义反序列化器:
class StatusDeserializer : JsonDeserializer<Status> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Status {
return try {
Status.valueOf(json.asString.uppercase())
} catch (e: IllegalArgumentException) {
Status.UNKNOWN
}
}
}
✅ 关键点:捕获
IllegalArgumentException
并返回默认值UNKNOWN
。
注册反序列化器:
val gson = GsonBuilder()
.registerTypeAdapter(Status::class.java, StatusDeserializer())
.create()
测试已知值:
@Test
fun `test known status value deserialization`() {
val json = """{"status": "SUCCESS"}"""
val response = gson.fromJson(json, ApiResponse::class.java)
assertEquals(Status.SUCCESS, response.status)
}
测试未知值:
@Test
fun `test unknown status value deserialization`() {
val unknownJson = """{"status": "unknown_value"}"""
val response = gson.fromJson(unknownJson, ApiResponse::class.java)
assertEquals(Status.UNKNOWN, response.status)
}
5. 总结
在反序列化枚举时处理未知值,是构建健壮系统的重要一环。本文演示了在 Kotlin 中使用 kotlinx.serialization、Jackson 和 Gson 三种方式来优雅地处理这个问题。
- ✅ Jackson 提供了原生支持,通过
@JsonEnumDefaultValue
和配置即可实现。 - ✅ kotlinx.serialization 和 Gson 则需要自定义反序列化器,但也能达到相同效果。
- ⚠️ 如果你使用的是 Jackson,推荐优先使用其内置机制;否则可以灵活选择其他库。
完整代码已上传至 GitHub:点击查看。