通过由元组列表组成的 Reads[T] 将 JsValue 转换为模型
Convert a JsValue to a model via Reads[T] which consists of a list of tuples
我有以下 class:
case class Model(elements: List[(String, String)])
现在我想使用 Reads[T]
用 JsValue
的值填充我的模型 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 _)
这是代码草稿:
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)))
您将无法对此处的所有内容使用 Play JSON 组合器,因为它们仅适用于固定字段映射。为了能够读取 elements
字段,您需要实现 Reads[List[(String, String)]]
。幸运的是,Play 已经有可用的 Reads[Map[A, B]]
(对于也有 Reads
的 A
和 B
类型),并且 Map[A, B]
可以很容易地转换为List[(A, B)]
(在Map
下面只是元组的集合)。
对于一次性的情况,我们可以使用 read[Map[String, String]]
和 map
到 List
。然后,我们可以将其映射到案例 class。假设以下 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]
是一种特殊情况,因为 class 只有一个字段。为了进一步了解它如何与 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)
现在我们可以使用组合器为新定义的 Model
编写一个 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))),)
我有以下 class:
case class Model(elements: List[(String, String)])
现在我想使用 Reads[T]
用 JsValue
的值填充我的模型 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 _)
这是代码草稿:
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)))
您将无法对此处的所有内容使用 Play JSON 组合器,因为它们仅适用于固定字段映射。为了能够读取 elements
字段,您需要实现 Reads[List[(String, String)]]
。幸运的是,Play 已经有可用的 Reads[Map[A, B]]
(对于也有 Reads
的 A
和 B
类型),并且 Map[A, B]
可以很容易地转换为List[(A, B)]
(在Map
下面只是元组的集合)。
对于一次性的情况,我们可以使用 read[Map[String, String]]
和 map
到 List
。然后,我们可以将其映射到案例 class。假设以下 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]
是一种特殊情况,因为 class 只有一个字段。为了进一步了解它如何与 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)
现在我们可以使用组合器为新定义的 Model
编写一个 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))),)