2017-01-10 20 views
1

Я пытаюсь создать читателей Json в приложении Play Framework (Scala). Проблема в том, что часть моего Json немного напуганна и требует дальнейшей обработки для извлечения значений. Например:Play Json - Создание сложного объекта

{ 
    "field1":"value1", 
    "field2":"value/1", 
    "num2":2 
} 

с тематическими классами:

case class Field1(text: String, fields: Field2) 
case class Field2(text: String, num: Int, num2: Int) 

В основном text и num поля для Field2 является производным от значения value/1, путем разделения текста. Вот функция разветвителя:

def splitter(path: String, num2: Int): Field2 = { 
    val split = path.split("\\") 
    Field2(split(0), split(1).toInt, num2) 
} 

Это довольно просто, фактическая функция сплиттера является гораздо более сложным. В принципе единственный способ построить этот объект Field2 - передать одну строку функции, которая выплескивает необходимый объект.

Как я могу создать считыватель для Field2 (и по расширению для Field1)?

Вот что я до сих пор:

object Field1 { 
    implicit val reader = (
     (__ \ "field1").read[String] and 
     (__).read[Field2] 
    ) (Field1.apply _) 
} 

object Field2 { 
    implicit val reader = (
     splitter((__ \ "field2").read[String], (__ \ "num2")) 
    ) // Obviously incorrect syntax + type mismatch, but this is roughly what I'm trying to accomplish. 
} 

ответ

0

Если вы используете нефункциональные синтаксис комбинатора становится немного проще, я думаю:

object Field2 { 
    implicit val reader = Reads[Field2] { json => 
    for { 
     path <- (json \ "field2").validate[String] 
     num2 <- (json \ "num2").validate[Int] 
    } yield splitter(path, num2) 
    } 
} 

Кроме того, если вы хотите разветвитель для дальнейшей проверки на входе имейте это в виду: JsResult[Field2]:

def splitter(path: String, num2: Int): JsResult[Field2] = { 
    val split = path.split("\\") 
    if (split.size != 2) { 
    JsError(s"$path must be of the form: {field}\\{num}") 
    } else { 
    Try(Field2(split(0), split(1).toInt, num2)).map(JsSuccess(_)).getOrElse { 
     JsError(s"${split(1)} is not a valid Int") 
    } 
    } 
} 

И затем изменить читателя:

object Field2 { 
    implicit val reader = Reads[Field2] { json => 
    for { 
     path <- (json \ "field2").validate[String] 
     num2 <- (json \ "num2").validate[Int] 
     field2 <- splitter(path, num2) 
    } yield field2 
    } 
} 

Если вы по-прежнему предпочитают использовать функциональный синтаксис, и вы не возражаете, отсутствие подтверждения, что разветвитель Пытается это:

def splitter(path: String, num2: Int): Field2 = { 
    val split = path.split("\\") 
    Field2(split(0), split(1).toInt, num2) 
} 

implicit val reader = (
    (__ \ "field2").read[String] and 
    (__ \ "num2").read[Int] 
)(splitter _) 

Я бы не рекомендовал это небезопасно (split(1) и toInt оба могут исключать исключения), а функциональный синтаксис может иметь проблемы с производительностью. См. https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/.

+0

Это прекрасно работает. Но нет ли способа использовать регулярный функциональный синтаксис? Я использовал это в других битах моего кода, и я бы хотел, чтобы все было согласовано. – Jeff

+0

@Jeff Проблема с функциональным синтаксисом заключается в том, что вы не можете ссылаться на предыдущие поля, чтобы вычислить вещи. Как только вы назовете применить, вы можете изменить созданный Reader, но вам нужен промежуточный тип для отслеживания данных, которые вы вычислили. Кроме того, при определенных обстоятельствах вы можете столкнуться с проблемами производительности с функциональным синтаксисом: https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/ – gregghz

+0

@Jeff Я добавил пример, который использует функциональный синтаксис. – gregghz

0

Я не знаю, зачем вам классы дела, но вы также можете преобразовать JSON для ваших нужд с

(__ \ "field2" \ "num2").json.copyFrom((__ \ "num2").json.pick) and 
    (__ \ "field2").json.update(
     of[String].map { o => 
     val split = o.split("/") 
     Json.obj(
      "text" -> split(0), 
      "num" -> split(1).toInt 
     ) 
     } 
    ) 
).reduce andThen (__ \ "num2").json.prune 

scala> j: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"field2":{"num2":2,"text":"value","num":1},"field1":"value1"},/num2) 

, а затем вы можете использовать Reads[Field1]

+0

Классы классов являются неотъемлемой частью уже разработанной системы.Кроме того, код, который я предоставил, представляет собой небольшое приближение к фактическому коду, который будет по одной строке, но намного, намного больше, поэтому преобразование json каждый раз не представляется возможным. – Jeff