2017-03-12 34 views
1

我有下面的類的列表:轉換一個JsValue到模型通過讀取[T]它由元組

case class Model(elements: List[(String, String)]) 

現在我想通過填補我的模型ModelJsValue的值Reads[T]。 JSON可以具有不同的鍵值對,它們在解組時是未知的,因此我想將它們作爲元組列表。

例如:

{ "foo": "bar", "barfoo": "foobar"} 

應該改爲:

List(("foo" -> "bar"), ("barfoo" -> "foobar")) 

的問題是,我不知道我怎麼能實現一種通配符功能的所有元素相匹配的JSON對象,但不是嵌套的或數組。

implicit val modelReads: Reads[Model] = (
     (JsPath \ "?").read[String] // and 
    // (JsPath \ "foo").read[String] // and <- key not known in advance 
    // (JsPath \ "barfoo").read[String] // <- key not known in advance 
     ) (Model.apply _) 

回答

4

您將無法在這裏使用JSON播放組合程序的一切,因爲它們只與固定字段映射工作。爲了能夠閱讀elements字段,您需要實施Reads[List[(String, String)]]。幸運的是,Play已經有Reads[Map[A, B]]可用(對於類型AB也有Reads),並且Map[A, B]可以很容易地轉換爲List[(A, B)](在Map下面只是元組的集合)。

對於一次性案例,我們可以使用read[Map[String, String]]map將其轉換爲List。然後,我們可以將它映射到案例類。假設下面的JSON結構:

val js = Json.parse("""{"element": { "foo": "bar", "barfoo": "foobar"}}""") 

你可以寫:

implicit val reads = (__ \ "elements").read[Map[String, String]] 
    .map(_.toList) 
    .map(tuples => Model(tuples)) 

,並嘗試一下:

scala> js.validate[Model] 
res8: play.api.libs.json.JsResult[Model] = JsSuccess(Model(List((foo,bar), (barfoo,foobar))),/elements) 

請注意以上Reads[Model]是怎樣的一個特例,因爲案例類只有一個字段。藉此遠一點,看看它是如何與JSON組合子玩,讓我們添加一個新的領域:

case class Model(elements: List[(String, String)], info: String) 

然後,我們也使我們的Reads的元組多一些通用的,所以它可以處理值任何類型的A其中Reads[A]可用的:

implicit def tupleReads[A](implicit rds: Reads[A]): Reads[List[(String, A)]] = 
    Reads.mapReads(rds).map(_.toList) 

現在我們可以寫一個Reads使用組合子的新定義Model,一樣的,你已經習慣了:

implicit val reads = (
    (__ \ "elements").read[List[(String, String)]] and 
    (__ \ "info").read[String] 
)(Model.apply _) 

想出來:

val js = Json.parse("""{"elements": { "foo": "bar", "barfoo": "foobar"}, "info": "test"}""") 

scala> js.validate[Model] 
res0: play.api.libs.json.JsResult[Model] = JsSuccess(Model(List((foo,bar), (barfoo,foobar)),test),) 

如果你的JSON結構只有看起來像{"foo": "bar", "barfoo": "foobar"}(無elements鍵),那麼我們仍然可以利用相同的通用Reads[List[(String, A)]],但將需要執行更多的自定義Reads[Model]將整個對象映射到一個模型字段。讓我們想映射上述JSON到:

Model(List(("foo" -> "bar"), ("barfoo" -> "foobar"))) 

我們需要將基本上是相同的,因爲我定義的第一個,但我們可以從它刪除JsPathReads[Model]

// Use `tupleReads` as defined above, restricted to `String` 
implicit val reads = tupleReads[String].map(tuples => Model(tuples)) 

它的工作原理如下:

val js = Json.parse("""{"foo": "bar", "barfoo": "foobar"}""") 

scala> js.validate[Model] 
res0: play.api.libs.json.JsResult[Model] = JsSuccess(Model(List((foo,bar), (barfoo,foobar))),) 
+0

夢幻般的答案。非常感謝邁克爾! –

0

下面是代碼草案:

val json = Json.parse(""" 
    { "foo": "bar", "barfoo": "foobar"} 
""") 

implicit val readMetaTag = 
    Reads(js => JsSuccess(
     Model(js.as[JsObject].fieldSet.map(
      tag => (tag._1, tag._2.as[String])).toList))) 

val model = json.as[Model] 

println("Model: " + model) 
//Model: Model(List((foo,bar), (barfoo,foobar))) 
+0

定義「Reads」時使用'as'並不安全。如果你試圖「驗證」一些不是'JsObject'的東西,它會拋出一個異常,這是你在使用一種所謂安全的方法時不應該指望的東西。 'val json = JsNumber(1); json.validate [模型]' –

+0

是的,你的答案,@MichaelZajac是輝煌的,thx爲此以及 –

相關問題