Jackson将字符串映射解析为已声明的类,作为字符串映射与映射如何使它创建声明的类的对象?

问题描述

我正在使用jackson-module-kotlin:2.11.2解析一些YAML。我试图产生包含映射的对象,其值是我声明的类的对象。相反,此地图包含的值是HashMaps。

这是我的声明:

import com.fasterxml.jackson.module.kotlin.readValue

object Parser {
    // ObjectMapper is thread safe as long as we don't mess with the config after this declaration.
    val mapper: ObjectMapper = ObjectMapper(YAMLFactory()).registerKotlinModule()
        .registerModule(JavaTimeModule())
        .registerModule(nullMapDeserialiserModule)
        .registerModule(SimpleModule().setDeserializerModifier(ValidatingDeserializerModifier()))
        // When parsing timestamps,we don't want to lose the offset @R_207_4045@ion
        .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
        // We would prefer an error if we're trying to store a float in an int
        .disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
        // If a primitive field (like int) is non-nullable (as in the Kotlin meaning),then we don't want nulls being converted to 0
        .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
        // Because enums Could change order in future versions (if we keep them in lexicographic order,for example),// we don't want the client to expect that they can give us the ordinal value of the enum.
        .enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
        // When serialising schedule expressions,don't include null values
        .setSerializationInclusion(JsonInclude.Include.NON_NULL)

    @Throws(JsonProcessingException::class)
    inline fun <reified T: Any> deserialise(yaml: String): T = mapper.readValue(yaml)

}

data class ListValue (
    val listValueKey: String,val someOtherValue: Int
)

data class ExpectedValue (
    val expectedValueKey: String,val list: List<ListValue>
)

data class TestClass (
    val testClassKey: String,@param:JsonDeserialize(contentAs = ExpectedValue::class)
    val testMap: Map<String,ExpectedValue>
)

这是我的测试用例:

@Test
fun `map parse test`() {
    val testObj: TestClass? = RuleParser.deserialise(
        //language=YAML
        """
          testClassKey: the-key
          testMap:
              key1:
                expectedValueKey: subKey1
                list: 
                  - listValueKey: someKey1
                    someOtherValue: 5
                  - listValueKey: anotherKey1
                    someOtherValue: 6
              key2:
                expectedValueKey: subKey2
                list: 
                  - listValueKey: someKey2
                    someOtherValue: 7
                  - listValueKey: anotherKey2
                    someOtherValue: 8
        """
    )

    assertTrue(testObj is TestClass)
    assert(testObj.testMap is HashMap)
    assertNotNull(testObj.testMap["key1"])
    assert(testObj.testMap["key1"] is ExpectedValue)
    assertEquals(
        ExpectedValue(
            expectedValueKey = "subKey1",list = listof(ListValue("someKey1",5),ListValue("anotherKey1",6))
        ),testObj.testMap["key1"]
    )
}

当前,该测试在最终断言上失败,并出现以下错误

Expected :ExpectedValue(expectedValueKey=subKey1,list=[ListValue(listValueKey=someKey1,someOtherValue=5),ListValue(listValueKey=anotherKey1,someOtherValue=6)])
Actual   :{expectedValueKey=subKey1,list=[{listValueKey=someKey1,someOtherValue=5},{listValueKey=anotherKey1,someOtherValue=6}]}

这显然不是我所期望的。如果我改为解析一个声明的类的列表,则此方法可以正常工作(示例测试如下)。

@Test
fun `list parse test`() {
    val testObj: ExpectedValue? = RuleParser.deserialise(
        //language=YAML
        """
            expectedValueKey: subKey1
            list: 
              - listValueKey: someKey1
                someOtherValue: 5
              - listValueKey: anotherKey1
                someOtherValue: 6
        """
    )

    assertTrue(testObj is ExpectedValue)
    assertTrue(testObj.list[0] is ListValue)
    assertEquals(
        ListValue("someKey1",testObj.list[0]
    )
}

因此,我有可能以这种方式解析通用列表,但无法解析地图,对此我感到有些惊讶。如何让Jackson创建我期望的地图值而不是HashMaps?

解决方法

您的解串器功能错误。您必须在readValue方法中使用经过修饰的通用类型:

inline fun <reified T: Any> deserialise(yaml: String): T = mapper.readValue(yaml,T::class.java)

,

虽然根本不需要注释是很奇怪的(因为声明类型的列表不需要注释),但是按如下方式使用注释也可以工作:

@field:JsonDeserialize(`as` = HashMap::class,contentAs = ExpectedValue::class)

一开始并不清楚,因为contentAs的javadoc没有提到as也是必需的。