在 JSValue 中重写嵌套搜索

Rewrite nested search in JSValue

我编写了以下函数 - 这对我来说有点 JAVA 方式,我想将其转换为更 Scala 的方式

@tailrec
private[services] final def nestedSearch(payload: JsValue, name: String): JsLookupResult = {
  val arrRegex = "([\w]+)\[([\d]+)\]".r

  def currLevelSearch(payload: JsValue, name: String): JsLookupResult = {
    name match {
      case arrRegex(arr, index) if Try(index.toInt).isSuccess => payload \ arr \ index.toInt // name is from the form arr[index], NOTE: by regex index is decimal => index.toInt is safe but still double-check
      case _ => payload \ name
    }
  }

  name indexOf "." match {
    case lastSearch if lastSearch < 0 => currLevelSearch(payload, name)
    case currSearch if currSearch >= 0 =>
      val `until first .` = name.substring(0, currSearch)
      val `after first .` = name.substring(currSearch + 1)
      currLevelSearch(payload, `until first .`) match {
        case JsDefined(newPayload) => nestedSearch(newPayload, `after first .`)
        case undefined: JsUndefined => undefined
      }
  }
}

该函数获取 JsValue 和名称作为输入,例如 name: a.b.c.d[0].e 并在嵌套中查找与此键匹配的 JsValue 字段,例如:

{
    "a": {
        "b": {
            "c": {
                "d": [
                    {
                        "firstElement": {
                            "e": "foundValue!"
                        }
                    }
                ]
            }
        }
    }
}

另一个想法是.split,我也可能认为有一些更好的方法来处理这个问题,实现如下:

  private[services] final def nestedSearch(payload: JsValue, name: String): JsLookupResult = {
    val arrRegex = "([\w]+)\[([\d]+)\]".r

    @tailrec def nestedSearchRec(keys: List[String])(seed: JsLookupResult): JsLookupResult = keys match {

      // Base cases:
      case Nil => seed
      case _ if seed.isEmpty => seed

      // Step case:
      case name :: tail =>
        nestedSearchRec(tail) {
          name match {
            case arrRegex(arr, index) => seed \ arr \ index.toInt
            case name => seed \ name
          }
        }
    }

    nestedSearchRec(name.split('.').toList)(JsDefined(payload))
  }

关于如何以适当的 Scala 方式重写这段代码有什么想法吗? 谢谢!

注意: 我正在使用 Scala 2.12.8play-json


感谢@Tomer Shetah 功能升级到:

private[services] final def nestedSearch(payload: JsValue, name: String): JsLookupResult = {
    val arrRegex = "([\w]+)\[([\d]+)\]".r
    JsPath {
      name.split("\.").toList.flatMap {
        case arrRegex(node, index) => List(KeyPathNode(node), IdxPathNode(index.toInt))
        case s => List(KeyPathNode(s))
      }
    }(payload) match {
      case Nil => JsUndefined(s"Could not find $name in $payload")
      case res :: _ => JsDefined(res)
    }
  }

你可以这样做:

val jsPath = JsPath \ "a" \ "b" \ "c" \ "d" \ "firstElement" \ "e"
val json = Json.parse(jsonString)

println(jsPath(json))

这将导致:

List("foundValue!")

请注意,\ 将获取数组中的所有元素。例如,如果在 d 的数组中将有一个兄弟:

{
    "firstElement": {
        "e": "foundValue!"
    }
}

结果将是:

List("foundValue!", "foundValue!2")

你要求第一个元素,所以要得到它你可以这样做:

jsPath(json).headOption

如果你需要将路径"a.b.c.d[0].firstElement.e"转换成JsPath,你可以这样做:

val path: List[PathNode] = pathString.split("\.").toList.flatMap {
  case s"$node[$index]" if index.toIntOption.isDefined =>
    List(KeyPathNode(node), IdxPathNode(index.toInt))
  case s=>
    List(KeyPathNode(s))
}

然后调用:

val jsPath = JsPath(path)
val json = Json.parse(jsonString)
println(jsPath(json))

结果为:

List("foundValue!")

代码 运行 在 Scastie