You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson-databind中Kotlin泛型列表反序列化异常问题

Kotlin泛型列表Jackson反序列化问题

我在应用中使用jackson-databind将Kotlin对象序列化为JSON,通过HTTP传输后在另一端反序列化为Kotlin对象,但特定场景下列表反序列化出现异常:deserializedResponse.result被反序列化为ArrayList<Map<String, String>>,原本的TotalStationPoint对象变成了属性映射。我希望它能还原为List<TotalStationPoint>,想先了解Jackson的惯用解决方式,再考虑自定义方案。

相关代码

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
interface ServerInteraction

data class GetJobResultResponse<T>(
    @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.CLASS)
    val result: T?
) : ServerInteraction

data class TotalStationPoint(val pointName: String, val location: Point3, val timestamp: Instant)

class LoadTotalStationDataSerializationTest {
    @Test
    fun loadTotalStationDataIsSerializedCorrectly() {
        val result: List<TotalStationPoint> = loadTotalStationData()
        val getJobResultResponse: GetJobResultResponse<List<TotalStationPoint>> = GetJobResultResponse(result)
        val serializedResponse: String = JsonSerializer.serialize(getJobResultResponse)

        /*
        Serializes to:
        {
          "type": "de.uni_freiburg.inatech.streem.image_converter.batch_job_executor.common.responses.GetJobResultResponse",
          "result": {
            "java.util.ArrayList": [
              {
                "pointName": "V1_22_0000000956",
                "location": {
                  "x": 10012.21984,
                  "y": 10002.558907,
                  "z": 2.342047000000001
                },
                "timestamp": 1670328663.04
              },
              {
                "pointName": "V1_22_0000000955",
                "location": {
                  "x": 10012.219816,
                  "y": 10002.559031,
                  "z": 2.3418279999999996
                },
                "timestamp": 1670328662.91
              }
              // ...
            ]
          }
        }
         */
        val deserializedResponse: GetJobResultResponse<*> = JsonSerializer.deserialize<GetJobResultResponse<*>>(serializedResponse)

        assertTrue((deserializedResponse.result as List<*>).first() is TotalStationPoint)
        assertEquals(getJobResultResponse, deserializedResponse)
    }

    private fun loadTotalStationData(): List<TotalStationPoint> {
        // Loads and parses a CSV file to List<TotalStationData>
        // ...
    }
}

惯用解决办法

1. 为列表元素添加类型标识

当前@JsonTypeInfo仅标记了外层容器(java.util.ArrayList)的类型,但没有为列表内的TotalStationPoint元素提供类型信息。给TotalStationPoint添加注解,让Jackson序列化时写入类型标识:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
data class TotalStationPoint(val pointName: String, val location: Point3, val timestamp: Instant)

这样序列化后的每个元素都会带上type字段,反序列化时Jackson就能识别出元素的具体类型。

2. 用TypeReference明确泛型类型

反序列化时,避免使用通配符GetJobResultResponse<*>,改用TypeReference指定完整的泛型参数类型:

val deserializedResponse = JsonSerializer.deserialize(
    serializedResponse, 
    object : TypeReference<GetJobResultResponse<List<TotalStationPoint>>>() {}
)

这种方式能让Jackson精准知晓result字段对应的是List<TotalStationPoint>,而非模糊的泛型类型。

3. 启用泛型类型ID特性

启用Jackson的ENABLE_USE_OF_GENERIC_TYPE_IDS特性,让序列化时写入完整的泛型参数类型信息:

val mapper = ObjectMapper().registerModule(KotlinModule())
mapper.enable(MapperFeature.ENABLE_USE_OF_GENERIC_TYPE_IDS)

同时调整GetJobResultResponse的注解配置:

data class GetJobResultResponse<T>(
    @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.CLASS, visible = true)
    val result: T?
) : ServerInteraction

该特性会让Jackson在处理泛型类型时,把完整的泛型参数信息写入JSON,反序列化时即可准确还原。

4. 用@JsonSubTypes声明子类(适用于有父类场景)

如果TotalStationPoint是某个父类的子类,可在父类中通过@JsonSubTypes声明所有可能的子类:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = TotalStationPoint::class, name = "totalStationPoint")
)
interface PointData

data class TotalStationPoint(val pointName: String, val location: Point3, val timestamp: Instant) : PointData

之后将GetJobResultResponse的泛型参数改为PointData相关类型,Jackson就能根据type字段自动匹配对应的子类类型。

内容的提问来源于stack exchange,提问作者vatbub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 15:55:04