首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用play-json对JsArrays进行排序

如何使用play-json对JsArrays进行排序
EN

Stack Overflow用户
提问于 2014-11-12 14:14:21
回答 2查看 2.4K关注 0票数 2

简单问题:

如何使用play-json (2.3.x)对某些JsValue中的所有JsValue进行排序(递归)?

我的用法:

考虑一个在内部使用Set[String]的应用程序,当请求数据时,输出JSON将该集序列化为JSON数组。命令不重要。

现在,如果一个人想要编写一些测试来覆盖这个功能,因为条目的顺序并不重要(毕竟它是一个集合)。在内部和概念上),我只想检查所有应该返回的内容,我可能希望将响应JSON与我显式创建的“预期”JSON对象进行比较。

基于这个确切的原因,我想对JSON数组进行排序,并比较JsValue's。如何编写这样的转换器?

编辑:

我已经成功地编写了一个转换器来满足我的需求,但它不能对某些JsValue中的每个JsValue进行排序。我会在这里发布,因为它可能对其他人有用,但这不是我想要的。

代码语言:javascript
复制
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
          })
        }
      }
    )
  }
)
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-11-12 18:27:37

您可以使用JsArray上的JsArray属性获取Seq[JsValue],然后任意排序,然后重新创建JsArray。例如:

代码语言:javascript
复制
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并检查是否相等:

代码语言:javascript
复制
Set(actual.value: _*) == Set(expected.value: _*)

或者把它们都分类:

代码语言:javascript
复制
val sortedSeq: JsArray => Seq[String] = array => array.value.map(_.toString).sorted
sortedSeq(actual) == sortedSeq(expected)

要递归地对任意JsArrays中的所有JsValue进行排序,它可能如下所示:

代码语言:javascript
复制
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]}}
票数 4
EN

Stack Overflow用户

发布于 2017-08-20 15:46:04

恐怕本的回答是不正确的。

我将通过为Ordering s定义一个JsValue类来处理这个问题,然后使用它的比较方法来验证相等性(这意味着这实际上应该是一个object --而不是一个匿名类,如示例所示)。

我们不必使用Ordering,我只是觉得它比简单的compareTo方法更方便一些。当然,也可以将这个类/对象定义为implicit

代码语言:javascript
复制
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
  }
}

我可能会将一些部分分割成私有/嵌套函数(这次我变懒了)。不管怎么说,让我们重温一下:

  1. 比较这两个值的类名,如果它们不相同,则返回它们之间的比较。
  2. 如果值是任何原始JSON类型,只需返回它们之间的比较。
  3. 如果值是数组,那么:
    1. 比较它们的大小,如果它们不相同,则返回大小之间的比较。
    2. 只有当数组的顺序不对每个数组进行排序时(具有相同的排序类;也就是说,这是递归的)。
    3. 压缩两个数组的元素(这样就可以得到一对元素的数组)。
    4. 找到其两个元素不相同的第一对,并返回它们的比较。
    5. 如果不存在这样的对,这意味着数组是相同的(返回0)。

  1. 如果值是映射(对象):
    1. 比较它们的大小,如果它们不相同,则返回大小之间的比较。
    2. 将映射转换为元组序列,并根据它们的键(元组的第一个元素)对这些序列进行排序。
    3. 压缩这两个序列的元组(这样就可以得到一对元组的数组)。
    4. 找到其元组不相同的第一对,并返回它们的比较。以以下方式比较这些元组:
      1. 比较它们的键(字符串),如果它们不一样,则返回它们的比较。
      2. 比较它们的值(JsValue,从而递归地使用相同的方法),如果它们不相同,则返回它们的比较。
      3. 否则,它们是一样的。

代码语言:javascript
复制
1. If no such pair exists, this means that the maps (objects) are the same (return 0).

请注意,尽管这种排序是一致的和确定性的,但它是相当任意的,并且没有传达太多的逻辑意义。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26889079

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档