Convert a JsValue to a model via Reads[T] which consists of a list of tuples

こ雲淡風輕ζ 提交于 2019-12-11 08:49:31

问题


I have the following class:

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

Now I want to fill my model Model with the values of a JsValue by using Reads[T]. The JSON could have different key values pairs which are unknown at the time of unmarshaling them and therefore I want to have them as a list of tuples.

For example:

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

Should become:

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

The problem is that I don't know how I can achieve a sort of wildcard function that matches all elements in a JSON object, but not nested ones or arrays.

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 _)

回答1:


You won't be able to use Play JSON combinators for everything here, as they only work with fixed field mappings. For you to be able to read the elements field, you would need to implement a Reads[List[(String, String)]]. Fortunately, Play already has a Reads[Map[A, B]] available (for types A and B that also have a Reads), and a Map[A, B] can easily be converted into a List[(A, B)] (underneath a Map is just a collection of tuples).

For a one-off case, we can use read[Map[String, String]] and map it to a List. Then, we can map that to the case class. Assuming the following JSON structure:

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

You can write:

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

And try it out:

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

Note that the Reads[Model] above is kind of a special case, because the case class only had a single field. To take this a bit further and see how it can play with JSON combinators, let's add a new field:

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

Then, let's also make our Reads for the tuples a little more generic, so that it can handle values of any type A where a Reads[A] is available:

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

Now we can write a Reads using combinators for the newly defined Model, the same as you're used to:

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

Trying it out:

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),)

If your JSON structure only looks like {"foo": "bar", "barfoo": "foobar"} (without an elements key), then we can still leverage the same generic Reads[List[(String, A)]], but will need to implement a more custom Reads[Model] to map an entire object to one model field. Let's we want to map the above JSON to:

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

The Reads[Model] we need will basically be the same as the first one I defined, except that we can drop the JsPath from it:

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

It works:

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))),)



回答2:


Here is the draft code:

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)))


来源:https://stackoverflow.com/questions/42753388/convert-a-jsvalue-to-a-model-via-readst-which-consists-of-a-list-of-tuples

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!