2017-01-10 83 views
1

我想在我的Play框架應用程序(Scala)中創建Json讀者。問題是,我的Json的一部分是有點時髦,並需要進一步處理來檢索值。例如:玩Json - 複雜的對象創建

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

與case類:

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

基本上爲Field2textnum字段從值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函數語法的問題在於,您無法引用先前的字段來計算事物。一旦你調用apply,你可以修改生成的Reader,但是你需要一箇中間類型來跟蹤你計算的數據。另外,在某些情況下,您可能會遇到功能語法的性能問題:https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/ – gregghz

+0

@Jeff我添加了一個使用函數語法的例子。 – gregghz

0

我不知道爲什麼你需要case類,但你也可以將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