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.serializationGson 则需要自定义反序列化器,但也能达到相同效果。
  • ⚠️ 如果你使用的是 Jackson,推荐优先使用其内置机制;否则可以灵活选择其他库。

完整代码已上传至 GitHub:点击查看


原始标题:Deserialize Enum While Ignoring Unknown Values in Kotlin