Проблемы с преобразованием объектов JSON в Scala

Я пытаюсь сделать простой пример сериализации класса в Scala, используя библиотеку json4s, но даже после долгих поисков в Интернете, к сожалению, я не смог найти любой удовлетворительный образец, который решил бы мою проблему.

По сути, у меня есть простой класс с именем Person, и я хочу извлечь экземпляр этого класса из строки JSON.

case class Person(
    val name: String,
    val age: Int,
    val children: Option[List[Person]]
)

Итак, когда я делаю это:

val jsonStr = "{\"name\":\"Socrates\", \"age\": 70}"
println(Serialization.read[Person](jsonStr))

Я получаю этот вывод:

"Person(Socrates,70,None)" // works fine!

Но когда у меня нет параметра age в строке JSON, я получаю эту ошибку:

Исключение в потоке «основной» org.json4s.package$MappingException: нет полезного значения для возраста

Я знаю, что класс Person имеет два обязательных параметра в своем конструкторе, но я хотел бы знать, есть ли способ сделать это преобразование с помощью синтаксического анализатора или чего-то подобного.

Кроме того, я пытался сделать этот парсер, но безуспешно.

Заранее благодарю за любую помощь.


person blzn    schedule 23.05.2015    source источник
comment
Что вы хотите получить в результате разбора JSON при отсутствии параметра age?   -  person Dan Getz    schedule 23.05.2015
comment
Очевидно, вам нужно использовать тип Option[Int] для возраста или создать собственный сериализатор: github.com/json4s/json4s#serializing-non-supported-types   -  person sap1ens    schedule 23.05.2015
comment
@dan-getz, я получу эти строки JSON из стороннего приложения, поэтому я не могу гарантировать, что все параметры будут присутствовать в их запросах, поэтому я просто пытаюсь выяснить, какие параметры у меня есть, чтобы сделать мой анализатор надежным. (Я начал изучать язык Scala буквально неделю назад).   -  person blzn    schedule 23.05.2015
comment
@ sap1ens, как я уже сказал, я пытался создать этот парсер, используя именно этот образец, но безуспешно. Спасибо, в любом случае.   -  person blzn    schedule 23.05.2015
comment
Каковы мои варианты? Вы можете оставить существующее исключение как есть, создать другое исключение, вернуть Option, вернуть специальное значение, использовать Option для параметра age, использовать специальное число age для обозначения неуказанного, вернуть Try или Either... список продолжается и продолжается. Вы просите помощи в кодировании синтаксического анализатора или решаете, что ваш код должен делать в первую очередь?   -  person Dan Getz    schedule 23.05.2015
comment
Мой вопрос касается как решения, что должен делать мой код, так и того, как сделать этот парсер. Я не вижу в этом ничего плохого, раз я не эксперт в этом языке.   -  person blzn    schedule 23.05.2015
comment
Хорошо, но мы не являемся экспертами в том, для чего вам нужен этот код, поэтому я и спросил. Не зная этого, мой первый инстинкт заключается в том, что ваш код выше работает отлично. Вы почему-то так не считаете. Разве это не из-за того, для чего вам нужно использовать код?   -  person Dan Getz    schedule 23.05.2015
comment
У меня много элементов, которые будут сериализованы одновременно, поэтому исключение может разрушить мою логику. Я хочу иметь возможность проверить, в порядке ли этот объект, не генерируя исключений, а возвращая только true или false в таком методе, как isValid. Это было бы удовлетворительно для архитектуры, о которой я думал.   -  person blzn    schedule 23.05.2015


Ответы (1)


Предполагая, что вы не хотите делать возраст необязательным, установив для его типа значение Option, вы можете написать собственный сериализатор, расширив CustomSerializer[Person]. Пользовательский сериализатор переводит функцию из Formats в кортеж PartialFunction[JValue, Person] (ваш десериализатор Person) и PartialFunction[Any, JValue] (ваш сериализатор).

Предполагая, что Person.DefaultAge является значением по умолчанию, которое вы хотите установить для возраста, если возраст не указан, тогда пользовательский сериализатор может выглядеть следующим образом:

object PersonSerializer extends CustomSerializer[Person](formats => ( {
  case JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: Nil) => Person(name, age.toInt, None)
  case JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: JField("children", JArray(children)) :: Nil) => Person(name, age.toInt, Some(children map (child => formats.customDeserializer(formats).apply(TypeInfo(classOf[Person], None), child).asInstanceOf[Person])))
  case JObject(JField("name", JString(name)) :: Nil) => Person(name, Person.DefaultAge, None)
  case JObject(JField("name", JString(name)) :: JField("children", JArray(children)) :: Nil) => Person(name, Person.DefaultAge, Some(children map (child => formats.customDeserializer(formats).apply(TypeInfo(classOf[Person], None), child).asInstanceOf[Person])))
}, {
  case Person(name, age, None) => JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: Nil)
  case Person(name, age, Some(children)) => JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: JField("children", formats.customSerializer(formats).apply(children)) :: Nil)
}))

Это, вероятно, можно было бы упростить, так как есть много повторений. Кроме того, может быть лучший способ рекурсивно вызывать сериализацию/десериализацию.

person Kulu Limpa    schedule 23.05.2015
comment
Большое спасибо, @KuluLimpa! Он работал мягко, как я и ожидал. - person blzn; 23.05.2015