【问题标题】:Extracting polymorphic types in json4s提取json4s中的多态类型
【发布时间】:2014-11-16 19:47:28
【问题描述】:

我正在使用 json4s 在我的 Scala 代码中处理 JSON 对象。我想将 JSON 数据转换为内部表示。以下学习测试说明了我的问题:

"Polimorphic deserailization" should "be possible" in {
    import org.json4s.jackson.Serialization.write
    val json =
      """
        |{"animals": [{
        |  "name": "Pluto"
        |  }]
        |}
      """.stripMargin
    implicit val format = Serialization.formats(ShortTypeHints(List(classOf[Dog], classOf[Bird])))
    val animals = parse(json) \ "animals"
    val ser = write(Animals(Dog("pluto") :: Bird(canFly = true) :: Nil))
    System.out.println(ser)
    // animals.extract[Animal] shouldBe Dog("Pluto") // Does not deserialize, because Animal cannot be constructed
}

假设有一个包含动物列表的 JSON 对象。 Animal 是一种抽象类型,因此无法实例化。相反,我想解析 JSON 结构以返回 DogBird 对象。他们有不同的签名:

case class Dog(name: String) extends Animal
case class Bird(canFly: Boolean) extends Animal

由于它们的签名不同,因此可以在 JSON 对象中没有类标记的情况下识别它们。 (准确地说,我收到的 JSON 结构没有提供这些标签)。

我尝试序列化 Animal 对象的列表(参见代码)。结果是:Ser: {"animals":[{"jsonClass":"Dog","name":"pluto"},{"jsonClass":"Bird","canFly":true}]}

可以看到,在序列化的时候,json4s添加了class-tagjsonClass

如何反序列化不提供此类标签的 JSON 对象?是否可以通过扩展 TypeHints 来实现?

我还发现了一个类似的问题:[json4s]:Extracting Array of different objects 的解决方案以某种方式使用泛型而不是子类化。但是,如果我理解正确,此解决方案不允许简单地传递 json 对象并具有内部表示。相反,我需要选择不是None 的表单(同时检查继承层次结构中的所有可能类型。这有点乏味,因为我在 JSON 结构中具有不同深度的多个多态类。

【问题讨论】:

  • 你找到答案了吗?我在这里面临同样的挑战......
  • 很遗憾,我没有找到答案。作为一种解决方法,我同意创建序列化 JSON 以添加类型提示的人的意见;但是如果你不能影响 JSON 方案,这显然不是一个解决方案。我仍然对答案感兴趣,并且对 json4s 的了解比撰写问题时更多,因此我将尝试提出解决方案。
  • @borck 感谢您提出这个问题。我发现扩展CustomSerializer 是一个相当简单的解决方案(尽管用于提取大型多态结构的代码可能会变得有点臃肿)。我希望这也能帮助您解决问题。

标签: scala json4s


【解决方案1】:

最终,在导致这个问题的项目中,我同意创建序列化 JSON 的人关于为所有多态类型添加类型提示的观点。回想起来,这个解决方案可能是最干净的,因为它支持 JSON 模式的未来扩展,而不会有引入歧义的危险。

不过,对于实际问题,有一个相当简单的解决方案(不仅仅是一种变通方法)。

org.json4s.Formats 类型是我们作用域中的一个隐式值,它提供了一个函数+(org.json4s.Serializer[A])。这个函数允许我们添加新的自定义序列化器。因此,对于每个多态超类型(在我们的例子中,这只涉及Animal),我们可以定义一个自定义序列化程序。在我们的示例中,我们有

trait Animal
case class Dog(name: String) extends Animal
case class Bird(canFly: Boolean) extends Animal

在没有类型提示的情况下运行的自定义序列化程序如下所示:

class AnimalSerializer extends CustomSerializer[Animal](format => ( {
  case JObject(List(JField("name", JString(name)))) => Dog(name)
  case JObject(List(JField("canFly", JBool(canFly)))) => Bird(canFly)
}, {
  case Dog(name) => JObject(JField("name", JString(name)))
  case Bird(canFly) => JObject(JField("canFly", JBool(canFly)))
}))

感谢+ 函数,我们可以添加多个自定义序列化程序,同时保留默认序列化程序。

case class AnimalList(animals: List[Animal])

val json =
  """
    |{"animals": [
    |  {"name": "Pluto"},
    |  {"name": "Goofy"},
    |  {"canFly": false},
    |  {"name": "Rover"}
    |  ]
    |}
  """.stripMargin
implicit val format = Serialization.formats(NoTypeHints) + new AnimalSerializer
println(parse(json).extract[AnimalList])

打印

AnimalList(List(Dog(Pluto), Dog(Goofy), Bird(false), Dog(Rover)))

【讨论】:

  • 这确实有效,感谢您进行调查...我觉得如果您掌握了 json 生产者,类型提示会更好地工作,尽管这确实会迅速膨胀...跨度>
  • 请注意,这是因为 Dog 和 Bird 的序列化类型不同(JString v/s JBool)。在更一般的情况下,您还需要序列化类型的名称以便区分它们。
  • 另请注意,您的自定义序列化程序类的 deserialize 部分要求 JSON 数据按照您组织模式匹配的方式排列。更多细节在这里:link
  • 为了减少样板代码的数量: 1) 当使用前面评论中的技术时,您可以在 customSerializer 类的反序列化部分提取案例类。 2) 对于 customSeriealizer 类的序列化程序部分,您可以使用 DSL 生成 JValues:: more info about DSL
猜你喜欢
  • 1970-01-01
  • 2018-03-31
  • 1970-01-01
  • 1970-01-01
  • 2018-09-27
  • 2021-06-04
  • 1970-01-01
  • 2014-06-14
  • 1970-01-01
相关资源
最近更新 更多