我有以下类:
case class Model(elements: List[(String, String)])
现在,我想通过使用Reads[T]
填充我的模型Model
。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 _)
您将无法在此处使用播放JSON组合器,因为它们仅与固定的字段映射一起使用。为了使您能够读取elements
字段,您需要实现Reads[List[(String, String)]]
。幸运的是,Play已经有一个Reads[Map[A, B]]
可用(对于也具有Reads
的类型A
和B
),并且可以轻松地将Map[A, B]
转换为List[(A, B)]
(在Map
下方是CC_12只是一个元组的集合)。
对于一次性情况,我们可以将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
,以便可以处理可用Reads[A]
的任何类型的A
的值:
implicit def tupleReads[A](implicit rds: Reads[A]): Reads[List[(String, A)]] =
Reads.mapReads(rds).map(_.toList)
现在,我们可以使用组合符为新定义的JsValue
1编写Reads
,与您使用的相同:
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")))
我们需要的Reads[Model]
基本上将与我定义的第一个相同,除了我们可以从中删除JsPath
:
// 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))),)
这是草稿代码:
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)))