13

I am using json4s to work with JSON objects in my Scala code. I want to convert JSON data to an internal representation. The following learning test illustrates my problem:

"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
}

Suppose there is a JSON object which has a list of Animals. Animal is an abstract type, and hence cannot be instantiated. Instead, I want to parse the JSON structure to return either Dog or Bird objects. They have a different signature:

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

Because their signature is distinct, they can be identified without having a class Tag in the JSON object. (To be precise, the JSON structure I receive does not provide those tags).

I tried to serialize a list of Animal objects (see the code). The result is: Ser: {"animals":[{"jsonClass":"Dog","name":"pluto"},{"jsonClass":"Bird","canFly":true}]}

As you can see, when serializing, json4s adds the class-tag jsonClass.

How can I deserialize a JSON object that does not provide such a tag? Is it possible to achieve this by extending TypeHints?

I also found a similar question: [json4s]:Extracting Array of different objects with a solution that somehow uses generics instead of subclassing. However, if I understand correctly, this solution does not allow to simply pass the json object and have an internal representation. Instead I would need to select the form that is not None (while checking all possible Types in the inheritance hiearchy. This is a bit tedious, since I have multiple Polymorphic classes at different depths in the JSON structure.

4

1 回答 1

16

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

然而,对于实际问题,存在一个相当简单的解决方案(不仅仅是一种解决方法)。

typeorg.json4s.Formats是我们范围内的一个隐式值,它提供了一个 function +(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)))
于 2014-11-26T23:40:19.580 回答