当前位置: 首页 > 知识库问答 >
问题:

Jackson:反序列化JSON到Scala ADT

倪培
2023-03-14

假设您有一个JSON,它看起来像这样:

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

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

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.JsonMappingExctive中的异常:无法构造...项目的实例,问题:抽象类型需要映射到具体类型,具有自定义反序列化器,或者在[源:[{"foo": 1,"bar":{"baz": 2}},{"foo": 3,"bar":{"baz": 4}}]中使用其他类型信息实例化;行:1,列:2](通过引用链:com.fasterxml.jackson.module.scala.deser.BuilderWrapper[0])

这相当清楚地说明了问题所在,但我不确定如何最好地解决它。

共有1个答案

公良高刚
2023-03-14

正如@Dima所指出的,我不认为存在一个涵盖所有情况的通用解决方案。此外,我不确定它是否存在,因为差异可能隐藏得很深,我怀疑足够聪明的人可能会因此制造出一个停顿的问题。然而,许多具体案例都可以解决。

首先,如果您控制双方(序列化和反序列化),您应该考虑使用JsonTypeIdResolver注释和一些TypeIdResolver子类,这些子类将把类型的名称放在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(", "))
}

哪个指纹

第一项(1,2),第三项(3,第四项)

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

 类似资料:
  • 我知道我可以创建一个单独的Report类,然后使用@JSONProperty将其嵌入到ReportResponse中。有没有一种方法可以避免这种情况,并用一个注释标记ReportResponse类,将它映射到JSON中的“Report”元素?

  • 我对解析json有一个问题。它有一个日期为exapmle-"2014-01-07"。当它解析并成为createUserRequest.getBirthday()时,它包含-"2014-01-07T04:00:00.000 04:00"。我需要它在createUserRequest对象中,然后我会用另一个对象断言它。问题是如何得到“2014-01-07”? 在CreateUserRequest中,我

  • 我使用Json-Jackson序列化/反序列化我的对象,并且与内置类配合得很好。 字符串、int等非常好。但是现在我必须序列化/反序列化以特定方式构建的对象。遵循声明: 如果我使用标准的序列化器/反序列化,它会向我显示一条错误消息,例如“java.lang.不支持的操作异常...”,因此它无法序列化/反序列化bobjs。 我如何以一般方式做到这一点,所以我不写3个序列化器/反序列化器,而是只告诉J

  • 问题内容: 使用Jackson 2,我正在寻找一种 通用的 方式将对象序列化为单个值(然后序列化它们,然后再填充该单个字段),而不必重复创建JsonSerializer / JsonDeserializer来处理每种情况。@JsonIdentityInfo批注非常接近,但由于我知道,它将始终对完整的子对象进行序列化,因此略微遗漏了该标记。 这是我想做的一个例子。给定的类: 我希望Order可以序列

  • 我收到来自第3方服务提供商的JSON响应,其中包含一系列对象。当我尝试使用Jackson api反序列化JSON时。我收到以下异常 我的回答是 我的POJO课是这样的 我正在尝试使用以下代码反序列化JSON 如果我试着去做 它在BEGIN_对象本身失败。 如何使用数组读取和反序列化JSON。我应该编写自己的反序列化器吗? 编辑如果我使用JSON字符串而不是流,那么我就能够取回所有Java对象。但为

  • 我目前正在开发一个Java web应用程序,它使用Magento REST API公开的JSON数据。api返回的数据示例如下: 我的应用程序中有一个Java类,如下所示: 我想对数据进行反序列化,并将其转换为,但我总是得到以下错误: 这是我用来将JSON响应反序列化为ArrayList的语句行: 有人能分享一些见解吗?我看到一些例子,返回的JSON对象前面没有任何ID。那是因为我做错了什么吗?非