Извлечение полиморфных типов в json4s

Я использую json4s для работы с объектами JSON в моем коде Scala. Я хочу преобразовать данные 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 для возврата объектов Dog или Bird. У них есть другая подпись:

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 jsonClass.

Как я могу десериализовать объект JSON, который не предоставляет такой тег? Можно ли достичь этого, расширив TypeHints?

Я также нашел аналогичный вопрос: [json4s]: Извлечение массива разных объектов с помощью решения, которое каким-то образом использует generics вместо подкласса. Однако, если я правильно понимаю, это решение не позволяет просто передать объект json и иметь внутреннее представление. Вместо этого мне нужно будет выбрать форму, которая не является None (при проверке всех возможных типов в наследовании hiearchy. Это немного утомительно, так как у меня есть несколько полиморфных классов на разных глубинах в структуре JSON.

Ответ 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)))