如何使用JsZipper将命名的JsArray转换为JsObject

时间:2014-02-22 10:52:03

标签: json scala playframework

让我们假设以下两个JSON片段之一:

{ "include": ["field1", "field2", "fieldN"] }
{ "exclude": ["field1", "field2", "fieldN"] }

我需要像这样转换include数组......

{ "field1": 1, "field2": 1, "fieldN": 1 }

...和这样的exclude数组:

{ "field1": 0, "field2": 0, "fieldN": 0 }

[仅供参考:我需要将输入JSON转换为Mongo的预测。]

以下是我目前的解决方案 - 我已将其实施为JsValue扩展程序:

object testTypeExtensions {

  implicit class TestJsExtensions(val json: JsValue) extends AnyVal {

    def toProjection: JsValue = {
      if (json \\ "include" nonEmpty)
        JsObject(for (field <- (json \ "include").as[List[JsString]])
        yield (field.value, JsNumber(1)))
      else if (json \\ "exclude" nonEmpty)
        JsObject(for (field <- (json \ "exclude").as[List[JsString]])
        yield (field.value, JsNumber(0)))
      else Json.obj()
    }
  }
}

上面的代码有效......

scala> val p = Json.obj("exclude" -> Json.arr("field1", "field2"))
p: play.api.libs.json.JsObject = {"exclude":["field1","field2"]}

scala> p.toProjection
res12: play.api.libs.json.JsObject = {"field1":0,"field2":0}

...但我确信使用JsZipper可以更好地写出来。

此外它不是很灵活,因为它只管理includeexclude键,而我还想管理其他类似的情况,如排序对象:

{ "asc": ["field1", "field2"] }
{ "desc": ["field1", "field2"] }

...转化为......

{ "field1": 1, "field2": 1 }

...和

{ "field1": -1, "field2": -1 }

那就是说,我想到的是一个管理任何类型的命名JSON数组的通用方法,如:

object testypeExtensions {

  implicit class TempJsExtensions(val json: JsValue) extends AnyVal {

    def namedArrayToObject(keys: String*): JsValue = {
      // how to implement it, possibly with JsZipper
    }

  }
}

namedArrayToObject方法应在当前JSON中搜索指定的keys,并为第一次匹配生成一个对象,就像我在本文开头所描述的那样,可能是JsZipper。这是对预期结果的模拟。

搜索excludeinclude并将第一场比赛作为JsObject返回:

scala> val p = Json.obj("exclude" -> Json.arr("field1", "field2"))
scala> p.namedArrayToObject("exclude", "include")
res12: play.api.libs.json.JsObject = {"field1":0,"field2":0}

与之前相同......但现在输入JSON包含include而不是exclude

scala> val p = Json.obj("include" -> Json.arr("field1", "field2"))
scala> p.namedArrayToObject("exclude", "include")
res12: play.api.libs.json.JsObject = {"field1":1,"field2":1}

搜索ascdesc并将第一场比赛作为JsObject返回:

scala> val p = Json.obj("desc" -> Json.arr("field1", "field2"))
scala> p.namedArrayToObject("asc", "desc")
res12: play.api.libs.json.JsObject = {"field1":-1,"field2":-1}

......等等。

如果没有匹配项,namedArrayToObject应返回空JsObject。任何关于如何以正确的方式实现这一点的建议都将非常感激。

2 个答案:

答案 0 :(得分:1)

您可以使用JSON transformations非常直接地执行此操作:

import play.api.libs.json._

def toObj(value: Int) = Reads.of[List[String]].map(
  keys => Json.toJson(keys.map(_ -> value).toMap)
)

val transformation =
  (__ \ 'include).json.update(toObj(1)) andThen
  (__ \ 'exclude).json.update(toObj(0))

我们可以定义一个示例对象并应用我们的转换:

val example = Json.parse("""{
  "include": ["field1", "field2", "field3"],
  "exclude": ["field4", "field5", "field6"]
}""")

val transformed = example.transform(transformation)

然后:

scala> transformed.foreach(Json.prettyPrint _ andThen println)
{
  "include" : {
    "field1" : 1,
    "field2" : 1,
    "field3" : 1
  },
  "exclude" : {
    "field4" : 0,
    "field5" : 0,
    "field6" : 0
  }
}

这与您想要的API不完全匹配,但它应该很容易适应,我建议远离隐式类业务,无论如何 - 它的组合性要低得多,并且处理无效输入不那么优雅。

答案 1 :(得分:0)

特拉维斯帮助我找到了方法......下面最终我的解决方案完全符合我的要求:

object testExtensions {

  implicit class testJsExtensions(val json: JsValue) extends AnyVal {

    def toParams(pairs: (String, Int)*): JsValue = {
      for (pair <- pairs) { json.getOpt(__ \ pair._1).map { values =>
        JsObject(for (field <- values.as[List[JsString]])
        yield (field.value, JsNumber(pair._2)))
      } match {
        case Some(params) => return params
        case _ =>
      }}
      Json.obj()
    }
  }
}

scala> example.toParams(("include", 1), ("exclude", 0))
res63: play.api.libs.json.JsValue = {"field1":1,"field2":1,"field3":1}

scala> example.toParams(("exclude", 0))
res64: play.api.libs.json.JsValue = {"field4":0,"field5":0,"field6":0}

再次感谢特拉维斯: - )