Search code examples
scalaplayframeworkjsonpath

Query and find the path of an JSON element value with Play JSON


I'm using Play Framework with Scala. I have the following structure for JSON being returned by an upstream server. This is just a representation

{
  "key": "some-key",
  "suspendData": {
    "d": [
      [
        "arbitrary-objects/strings"
      ],
      [
        "random-value",
        [
          "arbitrary-objects/strings"
        ],
        [
          [
            "value1",
            "important-item",
            [
              "important-key-1"
            ],
            "arbitrary-values/objects"
          ],
          [
            "value2",
            "important-item-2",
            [
              "important-key-2"
            ]
          ]
        ]
      ]
    ]
  }
}

The only facts that I have is that the data will be located somewhere within $.suspendData.d[1]. I know the value that I am searching for which is important-key-1. The value can be nested deeper or it could be on a different index within `d[1]. How do I approach the problem of finding whether

  1. The key exists in the JSON that has been obtained from the upstream server.
  2. The path where the key was found, so I can find other keys based on the same path. This is a reverse lookup problem.

I can currently only think of getting $.suspendData.d[1] and then loop to find whether such properties exist. Again, I can't find a proper way of doing this via JsPath. I know the JsonPath equivalent, but can't find the right way through existing Play JSON support.


Solution

  • Simple recursion, may be not so effective, but worked:

    def findPath(key: String, path: JsPath, xs: JsValue): Option[JsPath] = {
      xs match {
        case o:JsObject =>
          var res: Option[JsPath] = None
          val it = o.fields.iterator
          while (res.isEmpty && it.hasNext) {
            val e = it.next()
            res = findPath(key, path \ (e._1), e._2)
          }
          res
        case JsString(x) if x==key => 
          Some(path)
        case JsArray(x) =>
          var res: Option[JsPath] = None
          val it = x.zipWithIndex.iterator
          while (res.isEmpty && it.hasNext) {
            val e = it.next()
            res = findPath(key, path(e._2), e._1)
          }
          res
        case _ => None
      }
    }
    
    findPath("important-key-1", __, j)
    findPath("important-key-2", __, j)
    findPath("important-key-3", __, j)
    findPath("some-key", __, j)
    
    scala> res62: Option[play.api.libs.json.JsPath] = Some(/suspendData/d(1)(2)(0)(2)(0))
    
    scala> res63: Option[play.api.libs.json.JsPath] = Some(/suspendData/d(1)(2)(1)(2)(0))
    
    scala> res64: Option[play.api.libs.json.JsPath] = None
    
    scala> res65: Option[play.api.libs.json.JsPath] = Some(/key)