自定义Kotlinx序列化器触发JsonDecodingException:字符串反序列化失败
问题:自定义Kotlin序列化器反序列化字符串抛出异常
异常信息
kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 10: Expected quotation mark '"', but had 't' instead at path: $.plan JSON input: {"plan": "test"}
相关代码
待序列化对象
@Serializable data class Test( val plan: PlanAsString? )
自定义序列化器
typealias PlanAsString = @Serializable(with = PlanSerializer::class) String class PlanSerializer : KSerializer<String?> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("StringSerializer", PrimitiveKind.STRING) @OptIn(ExperimentalSerializationApi::class) override fun deserialize(decoder: Decoder): String? { kotlin.runCatching { val plan = decoder.decodeSerializableValue(Test.serializer()) return plan.plan }.onFailure { return decoder.decodeString() } return decoder.decodeNull() } @OptIn(ExperimentalSerializationApi::class) override fun serialize(encoder: Encoder, value: String?) { return value?.let { encoder.encodeString(value) } ?: encoder.encodeNull() } }
测试代码
Json.decodeFromString<Test>("{\"plan\": \"test\"}") // 抛出上述异常 Json.decodeFromString<Test>("{\"plan\": {\"name\": \"basic\"}}") // 可正常反序列化
问题原因
- 解码器位置偏移:当处理字符串值时,你先尝试用
Test.serializer()解码对象结构,此时解码器指针会移动到字符串内部(比如"test"的t位置),解码失败后再调用decodeString(),解码器位置已不正确,导致找不到引号的异常。 - 循环引用:
Test类包含PlanAsString字段,而PlanAsString又依赖当前序列化器,调用Test.serializer()会触发无限递归解码。
解决方案
通过判断JSON元素类型,分别处理字符串、对象、空值三种情况,避免解码器位置错误和循环引用:
typealias PlanAsString = @Serializable(with = PlanSerializer::class) String class PlanSerializer : KSerializer<String?> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("PlanSerializer", PrimitiveKind.STRING) @OptIn(ExperimentalSerializationApi::class) override fun deserialize(decoder: Decoder): String? { val jsonDecoder = decoder as JsonDecoder val element = jsonDecoder.decodeJsonElement() return when (element) { is JsonNull -> null is JsonPrimitive -> { if (element.isString) element.content else null } is JsonObject -> { // 从对象中提取需要的字段(示例为"name")作为字符串返回 element["name"]?.jsonPrimitive?.content } else -> null } } override fun serialize(encoder: Encoder, value: String?) { value?.let(encoder::encodeString) ?: encoder.encodeNull() } }
代码说明
- 将通用
Decoder转为JsonDecoder,直接获取当前JSON元素,避免解码器位置偏移 - 分类型处理:
JsonNull:直接返回nullJsonPrimitive:判断为字符串则返回内容JsonObject:提取目标字段(如name)的字符串值返回
验证
修改后两个测试用例均可正常运行:
Json.decodeFromString<Test>("{\"plan\": \"test\"}") Json.decodeFromString<Test>("{\"plan\": {\"name\": \"basic\"}}")
内容的提问来源于stack exchange,提问作者Sk1X1
相关产品推荐
相关产品推荐

