jackson 如何反序列化一个字段并区分值是空还是不存在?

5tmbdcev  于 2022-11-23  发布在  其他
关注(0)|答案(1)|浏览(128)

当我反序列化到我的类中时,对于一些字段,我希望能够区分值是缺失还是空。例如,{"id": 5, "name": null}应该被认为不同于{"id": 5}
我遇到过kotlinx.serialisationRust's serde的解决方案,但到目前为止,我还在努力在Jackson实现这一点。
我将使用这个类作为示例:

data class ResponseJson(
        val id: Int,
        @JsonDeserialize(using = OptionalPropertyDeserializer::class)
        val name: OptionalProperty<String?>
    )

OptionalProperty字段的定义:

sealed class OptionalProperty<out T> {
    object Absent : OptionalProperty<Nothing>()
    data class Present<T>(val value: T?) : OptionalProperty<T>()
}

我编写了一个自定义反序列化器:

class OptionalPropertyDeserializer :
    StdDeserializer<OptionalProperty<*>>(OptionalProperty::class.java),
    ContextualDeserializer
{
    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): OptionalProperty<*> {
        println(p.readValueAs(ctxt.contextualType.rawClass))
        return OptionalProperty.Present(p.readValueAs(ctxt.contextualType.rawClass))
    }

    override fun getNullValue(ctxt: DeserializationContext?) = OptionalProperty.Present(null)
    override fun getAbsentValue(ctxt: DeserializationContext?) = OptionalProperty.Absent

    override fun createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer<*> {
        println(property.type.containedType(0))
        return ctxt.findContextualValueDeserializer(property.type.containedType(0), property)
    }
}

最后,我的ObjectMapper设置:

val messageMapper: ObjectMapper = jacksonObjectMapper()
    .disable(ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
    .disable(ACCEPT_FLOAT_AS_INT)
    .enable(FAIL_ON_NULL_FOR_PRIMITIVES)
    .enable(FAIL_ON_NUMBERS_FOR_ENUMS)
    .setSerializationInclusion(NON_EMPTY)
    .disable(WRITE_DATES_AS_TIMESTAMPS)

现在,我尝试反序列化一些JSON:

@Test
    fun deserialiseOptionalProperty() {
        assertEquals(
            ResponseJson(5, OptionalProperty.Present("fred")),
            messageMapper.readValue(
                //language=JSON
                """
                  {
                    "id": 5,
                    "name": "fred"
                  }
                """.trimIndent()
            )
        )
    }

出现以下异常:

com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `serialisation_experiments.JacksonTests$ResponseJson`, problem: argument type mismatch
 at [Source: (String)"{
  "id": 5,
  "name": "fred"
}"; line: 4, column: 1]

这里的“参数类型不匹配”是什么意思?我假设我对自定义反序列化器做了一些不正确的事情,但是正确的方法是什么呢?

dm7nw8vv

dm7nw8vv1#

我使用了OptionalProperty类的一个实现,它有一个字段来保存值,还有一个标志来指示值是否被设置。每当值被更改时,使用自定义的setter,该标志将被设置为true。有了这样一个类和默认的jacksonObjectMapper(),我就能够获得所有场景的反序列化工作名称指定,name null和name missing.下面是我最终得到的类:

可选属性:

class OptionalProperty {
    var value: Any? = null
        set(value) {
            field = value
            valueSet = true
        }
    var valueSet: Boolean = false
}

回应:

data class ResponseJson (
    @JsonDeserialize(using = OptionalPropertyDeserializer::class)
    val id: OptionalProperty,
    @JsonDeserialize(using = OptionalPropertyDeserializer::class)
    val name: OptionalProperty
)

可选属性反序列化程序:

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer

class OptionalPropertyDeserializer : JsonDeserializer<OptionalProperty>() {
    override fun deserialize(parser: JsonParser, context: DeserializationContext): OptionalProperty {
        var property = OptionalProperty()
        property.value = parser.readValueAs(Any::class.java)
        return property
    }

    override fun getNullValue(context: DeserializationContext): OptionalProperty {
        var property = OptionalProperty()
        property.value = null
        return property
    }

    override fun getAbsentValue(context: DeserializationContext): OptionalProperty {
        return OptionalProperty()
    }
}

测试:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.Test

class ResponseJsonTest {
    var objectMapper = jacksonObjectMapper()

    @Test
    fun `test deserialization - id and name are non-null`() {
        var responseJson = objectMapper.readValue(
            """
                  {
                    "id": 5,
                    "name": "test"
                  }
                """.trimIndent(),
            ResponseJson::class.java
        )
        assert(responseJson.id.value == 5)
        assert(responseJson.id.valueSet)
        assert(responseJson.id.value is Int)
        assert(responseJson.name.value == "test")
        assert(responseJson.name.valueSet)
        assert(responseJson.name.value is String)
    }

    @Test
    fun `test deserialization - name is null`() {
        var responseJson = objectMapper.readValue(
            """
                  {
                    "id": 5,
                    "name": null
                  }
                """.trimIndent(),
            ResponseJson::class.java
        )
        assert(responseJson.id.value == 5)
        assert(responseJson.id.valueSet)
        assert(responseJson.id.value is Int)
        assert(responseJson.name.value == null)
        assert(responseJson.name.valueSet)
    }

    @Test
    fun `test deserialization - name is absent`() {
        var responseJson = objectMapper.readValue(
            """
                  {
                    "id": 5
                  }
                """.trimIndent(),
            ResponseJson::class.java
        )
        assert(responseJson.id.value == 5)
        assert(responseJson.id.valueSet)
        assert(responseJson.id.value is Int)
        assert(responseJson.name.value == null)
        assert(!responseJson.name.valueSet)
    }

    @Test
    fun `test deserialization - id is null`() {
        var responseJson = objectMapper.readValue(
            """
                  {
                    "id": null,
                    "name": "test"
                  }
                """.trimIndent(),
            ResponseJson::class.java
        )
        assert(responseJson.id.value == null)
        assert(responseJson.id.valueSet)
        assert(responseJson.name.value == "test")
        assert(responseJson.name.valueSet)
        assert(responseJson.name.value is String)
    }

    @Test
    fun `test deserialization - id is absent`() {
        var responseJson = objectMapper.readValue(
            """
                  {
                    "name": "test"
                  }
                """.trimIndent(),
            ResponseJson::class.java
        )
        assert(responseJson.id.value == null)
        assert(!responseJson.id.valueSet)
        assert(responseJson.name.value == "test")
        assert(responseJson.name.valueSet)
        assert(responseJson.name.value is String)
    }
}

包含主类和测试类的完整项目可以在github中找到

相关问题