【问题标题】:Jackson: deserialize JSON to Scala ADTJackson:将 JSON 反序列化为 Scala ADT
【发布时间】:2017-12-23 19:59:34
【问题描述】:

假设您有一个如下所示的 JSON:

[{"foo": 1, "bar": 2}, {"foo": 3, "bar": {"baz": 4}}]

尝试使用 Scala sum 类型来表示这一点似乎很自然:

sealed trait Item
case class IntItem(foo: Int, bar: Int) extends Item
case class Baz(baz: Int)
case class BazItem(foo: Int, bar: Baz) extends Item

我的问题是:是否可以使用 Jackson 的 Scala 模块将上面的 JSON 序列化为 List[Item]

我的尝试:

val string = "[{\"foo\": 1, \"bar\": 2}, {\"foo\": 3, \"bar\": {\"baz\": 4}}]"
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.readValue[List[Item]](string)

例外:

线程“main”com.fasterxml.jackson.databind.JsonMappingException 中的异常:无法构造 ...Item 的实例,问题:抽象类型要么需要映射到具体类型,要么具有自定义反序列化器,要么被实例化附加类型信息 在 [来源:[{“foo”:1,“bar”:{“baz”:2}},{“foo”:3,“bar”:{“baz”:4}}];行:1,列:2](通过引用链:com.fasterxml.jackson.module.scala.deser.BuilderWrapper[0])

这很清楚问题是什么,但我不确定如何最好地解决它。

【问题讨论】:

  • 我要补充一点,如果您将 "bar": 2 替换为 "bar": {"baz": 2}List[Item] 替换为 List[BazItem],那么一切正常,所以这确实是 ADT 的问题。
  • 我只是想知道......如果你要实现Jackson,你会如何猜测每个列表元素应该被解析成哪种类型。

标签: scala jackson


【解决方案1】:

正如@Dima 指出的那样,我认为不存在涵盖所有情况的通用解决方案。此外,我不确定它是否存在,因为差异可能隐藏在任意深处,我怀疑足够聪明的人可以从中创建halting problem。但是可以解决很多具体情况。

首先,如果您控制双方(序列化和反序列化),您应该考虑使用带有TypeIdResolver 子类的JsonTypeIdResolver 注释,这些子类会将类型名称放入JSON 本身。

如果您不能使用JsonTypeIdResolver,可能唯一的解决方案是按照错误提示推出您的自定义JsonDeserializer。您在问题中提供的示例可以通过以下方式处理:

sealed trait Item
case class IntItem(foo: Int, bar: Int) extends Item
case class Baz(baz: Int)
case class BazItem(foo: Int, bar: Baz) extends Item



import com.fasterxml.jackson.core._
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.util.TokenBuffer
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.node._
import com.fasterxml.jackson.databind.exc._
import java.io.IOException

class ItemDeserializer() extends StdDeserializer[Item](classOf[Item]) {

  @throws[IOException]
  @throws[JsonProcessingException]
  def deserialize(jp: JsonParser, ctxt: DeserializationContext): Item = {
    // 1) Buffer current state of the JsonParser
    // 2) Use firstParser (from the buffer) to parser whole sub-tree into a generic JsonNode
    // 3) Analyze tree to find out the real type to be parser
    // 3) Using the buffer roll back history and create objectParser to parse the sub-tree as known type
    val tb = new TokenBuffer(jp, ctxt)
    tb.copyCurrentStructure(jp)

    val firstParser = tb.asParser
    firstParser.nextToken
    val curNode = firstParser.getCodec.readTree[JsonNode](firstParser)

    val objectParser = tb.asParser
    objectParser.nextToken()

    val bar = curNode.get("bar")
    if (bar.isInstanceOf[IntNode]) {
      objectParser.readValueAs[IntItem](classOf[IntItem])
    }
    else if (bar.isInstanceOf[ObjectNode]) {
      objectParser.readValueAs[BazItem](classOf[BazItem])
    }
    else {
      throw ctxt.reportBadDefinition[JsonMappingException](classOf[Item], "Unknown subtype of Item") // Jackson 2.9
      //throw InvalidDefinitionException.from(jp, "Unknown subtype of Item", ctxt.constructType(classOf[Item])) // Jackson 2.8
    }
  }
}

然后你可以像下面这样使用它

def test() = {
  import com.fasterxml.jackson.module.scala._
  import com.fasterxml.jackson.module.scala.experimental._

  val mapper = new ObjectMapper() with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)

  // add our custom ItemDeserializer
  val module = new SimpleModule
  module.addDeserializer(classOf[Item], new ItemDeserializer)
  mapper.registerModule(module)

  mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

  val string = "[{\"foo\": 1, \"bar\": 2}, {\"foo\": 3, \"bar\": {\"baz\": 4}}]"

  val list = mapper.readValue[List[Item]](string)
  println(list.mkString(", "))
}

打印出来的

IntItem(1,2), BazItem(3,Baz(4))

ItemDeserializer 中的主要技巧是使用TokenBuffer 解析 JSON 两次:第一次分析 JSON-tree 并找出它应该被解析为什么类型,第二次实际解析对象已知类型。

【讨论】:

    猜你喜欢
    • 2015-02-28
    • 2019-11-17
    • 2013-08-04
    • 2021-06-11
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    • 1970-01-01
    • 2023-03-13
    相关资源
    最近更新 更多