2014-09-22 8 views
10

Я использую json4s для работы с объектами JSON в моем коде Scala. Я хочу преобразовать данные JSON во внутреннее представление. Следующий учебный тест иллюстрирует мою проблему:Извлечение полиморфных типов в json4s

"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]:Extracting Array of different objects с решением, которое каким-то образом использует дженерики вместо подкласса. Однако, если я правильно понимаю, это решение не позволяет просто передать объект json и иметь внутреннее представление. Вместо этого мне нужно будет выбрать форму, которая не является None (при проверке всех возможных типов в наследовании hiearchy. Это немного утомительно, так как у меня есть несколько полиморфных классов на разных глубинах в структуре JSON.

+0

ли вы когда-нибудь найти ответ на этот вопрос? Я столкнулся с такой же проблемой здесь ... – borck

+0

К сожалению, я не нашел ответа.В качестве обходного пути я согласился с парнем, который создал сериализованный JSON, чтобы добавить подсказки типов; но это, очевидно, не решение, если вы не можете повлиять на схему JSON. Я по-прежнему интересуюсь ответом и имею немного больше знаний о json4s, чем у меня на момент написания вопроса, поэтому я постараюсь придумать решение. –

+0

@borck Спасибо за оживление вопроса. Я обнаружил, что расширение «CustomSerializer» является довольно простым решением (хотя код для извлечения больших полиморфных структур может немного раздуваться). Я надеюсь, что это также поможет вам решить вашу проблему. –

ответ

15

В конечном счете, на проект, который приводит к этому вопросу, я согласился с парнем, создающим сериализованный 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))) 
+2

Это действительно работает, спасибо за то, что вы провели исследование ... Я чувствую, что подсказки типа работают лучше, если у вас есть хватка на json-продюсере, хотя это действительно будет быстро раздуваться ... – borck

Смежные вопросы