Jackson Deserializer 在失败时默认为 null

Jackson Deserializer default to null on failure

我遇到一个问题,上游服务正在发出请求,发出的请求中约有 5% 部分格式不正确。例如,我得到的不是可空的 属性 作为我期望的对象类型,而是一个随机字符串。

data class FooDTO(
  val bar: BarDTO?,
  val name: String
)

data class BarDTO(
  val size: Int
)

但我得到的 payload 看起来像

{
  "name": "The Name",
  "bar": "uh oh random string instead of object"
}

我不想在发生这种情况时使请求失败,因为正确的数据部分对我的目的仍然有用,所以我想做的只是将反序列化失败默认为 null.我的 FooDTO 中也有一些不同的子对象可以执行此操作,因此我想要一种通用方法来解决这些特定领域的问题。我知道我可以像下面这样编写一个自定义解串器来一次性解决它。

class BarDtoDeserializer @JvmOverloads constructor(vc: Class<*>? = null) : StdDeserializer<BarDTO?>(vc) {
    override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): AnalysisDTO? {
        return try {
            val node = jp.codec.readTree<JsonNode>(jp)
            BarDTO(size = node.get("size").numberValue().toInt())
        } catch (ex: Throwable) {
            null
        }
    }
}

然后我可以用 @JsonDeserialize(using=BarDtoDeserializer::class) 装饰我的 BarDTO 对象以强制它使用该反序列化器。我希望做的是有一些通用的方法来做到这一点。

感谢这个答案 I've found that a DeserializationProblemHandler 可用于 return 'null' 无效数据。

在您的实例中,覆盖 handleMissingInstantiator() 函数。如果其他负载有其他类型的错误数据,则可能需要其他覆盖。

此外,我认为 CoercionConfig 可能是答案,但我无法让它工作或找到合适的文档。那可能是另一条路。


import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue


data class FooDTO(
  val name: String,
  val bar: BarDTO?,
)

data class BarDTO(
  val size: Int
)

fun main() {
  val mapper = jacksonObjectMapper()

//  mapper
//    .coercionConfigFor(LogicalType.Textual)
//    .setCoercion(CoercionInputShape.String, CoercionAction.AsNull)

  mapper.addHandler(object : DeserializationProblemHandler() {
    override fun handleMissingInstantiator(
      ctxt: DeserializationContext?,
      instClass: Class<*>?,
      valueInsta: ValueInstantiator?,
      p: JsonParser?,
      msg: String?
    ): Any? {
      println("returning null for value, $msg")
      return null
    }
  })

  val a: FooDTO = mapper.readValue(
    """
      {
        "name": "I'm variant A",
        "bar": "uh oh random string instead of object"
      }
    """
  )
  val b: FooDTO = mapper.readValue(
    """
      {
        "name": "I'm variant B",
        "bar": {
          "size": 2     
        }
      }
    """
  )

  println(a)
  println(b)
  assert(a.bar == null)
  assert(a.bar?.size == 2)

}

输出:

returning null for value, no String-argument constructor/factory method to deserialize from String value ('uh oh random string instead of object')
FooDTO(name=I'm variant A, bar=null)
FooDTO(name=I'm variant B, bar=BarDTO(size=2))

Process finished with exit code 0