如何使用play-json(2.3.x)对某些JsValue(递归)中的所有JsArrays进行排序?
考虑在内部使用Set[String]
的应用,并在请求数据时,
输出JSON将该集序列化为JSON数组。订单并不重要。
现在,如果有人想编写一些测试来覆盖这个功能,因为项目的顺序并不重要(毕竟它是一个集合。内部和概念上),我要检查的是所有返回的内容它应该,我可能想要将响应JSON与我明确创建的“预期”JSON对象进行比较。
出于这个原因,我想对JSON数组进行排序,并比较JsValue
。
怎么会写这样的变压器?
我设法编写了一个满足我需求的转换器,但它不会对某些JsArry
中的每个JsValue
进行排序。我会在这里发布,因为它可能对其他人有用,但它不是我要求的。
val jsonSortTransformer = (__ \ 'fields).json.update(
Reads.JsObjectReads.map{
case JsObject(xs) => JsObject(
xs.map{
case (n,jv) => {
n -> (jv match {
case JsArray(arr) if arr.forall(_.isInstanceOf[JsString]) => JsArray(arr.sortBy(_.as[String]))
case _ => jv
})
}
}
)
}
)
答案 0 :(得分:3)
您可以使用value
上的JsArray
属性获取Seq[JsValue]
,然后进行任意排序,然后重新创建JsArray
。例如:
scala> myJsArray
play.api.libs.json.JsArray = ["11","4","5","1","22","2"]
scala> JsArray(myJsArray.value.sortBy(_.as[JsString].value.toInt))
play.api.libs.json.JsArray = ["1","2","4","5","11","22"]
如果您所做的只是尝试比较您知道的集合的实际值和期望值,您也可以在两个属性上使用value
,构建Set
并检查为了平等:
Set(actual.value: _*) == Set(expected.value: _*)
或者对它们进行排序:
val sortedSeq: JsArray => Seq[String] = array => array.value.map(_.toString).sorted
sortedSeq(actual) == sortedSeq(expected)
要对任意JsArrays
中的所有JsValue
进行递归排序,它可能类似于:
def sortArrays(json: JsValue): JsValue = json match {
case JsObject(obj) => JsObject(obj.toMap.mapValues(sortArrays(_)).toList)
case JsArray(arr) => JsArray(arr.map(sortArrays).sortBy(_.toString))
case other => other
}
scala> myObj
play.api.libs.json.JsValue = {"a":[2,1],"b":[{"c":[3,2]},{"d":[4,3]}],"e":{"f":[5,4]}}
scala> sortArrays(myObj)
play.api.libs.json.JsValue = {"a":[1,2],"b":[{"c":[2,3]},{"d":[3,4]}],"e":{"f":[4,5]}}
答案 1 :(得分:0)
我担心@Ben的答案是错误的。
我会通过为Ordering
定义一个JsValue
类来解决这个问题,然后使用它的比较方法来验证相等性(这意味着它实际上应该是object
- 而不是一个匿名类,如示例所示。
一个人不必使用Ordering
,我发现它比简单的compareTo
方法更方便。当然,也可以将此类/对象定义为implicit
。
val jsonOrdering: Ordering[JsValue] = new Ordering[JsValue]() {
override def compare(x: JsValue, y: JsValue): Int = {
x.getClass.getName.compareTo(y.getClass.getName) match {
case 0 =>
(x, y) match {
case (JsNull, JsNull) => 0
case (JsString(valueX), JsString(valueY)) =>
valueX.compareTo(valueY)
case (JsNumber(valueX), JsNumber(valueY)) =>
valueX.compare(valueY)
case (JsBoolean(boolX), JsBoolean(boolY)) =>
boolX.compareTo(boolY)
case (JsArray(elementsX), JsArray(elementsY)) =>
elementsX.size.compareTo(elementsY.size) match {
case 0 =>
elementsX
// .sorted(this) // uncomment if array order DOES NOT matter
.zip(elementsY
// .sorted(this) // uncomment if array order DOES NOT matter
)
.view
.map {
case (elementX, elementY) => compare(elementX, elementY)
}
.find(_ != 0)
.getOrElse(0)
case nonZero => nonZero
}
case (JsObject(fieldsX), JsObject(fieldsY)) =>
fieldsX.size.compareTo(fieldsY.size) match {
case 0 =>
fieldsX.toSeq
.sortBy(_._1)
.zip(fieldsY.toSeq.sortBy(_._1))
.view
.flatMap {
case ((keyX, valueX), (keyY, valueY)) =>
Seq(keyX.compareTo(keyY), compare(valueX, valueY))
}
.find(_ != 0)
.getOrElse(0)
case nonZero => nonZero
}
}
case nonZero => nonZero
}
}
我可能会将一些部分分成私有/嵌套函数(这次我很懒)。无论如何,让我们来看看:
JsValue
,因此递归使用相同的方法),如果它们不相同则返回它们的比较。请注意,虽然这种顺序是一致的和确定性的,但它是非常随意的,并没有传达很多逻辑意义。