在 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.8
和 play-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。
我编写了以下函数 - 这对我来说有点 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.8
和 play-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。