使用Play JSON查找具有动态键的JSON元素的路径

时间:2018-10-31 06:03:57

标签: json scala playframework

我正在使用带有Scala的Play Framework。我具有以下JSON结构:

{
    "a": 1540554574847,
    "b": 2,
    "c": {
    "pep3lpnp1n1ugmex5uevekg5k20wkfq3": {
    "a": 1,
    "b": 1,
    "c": 1,
    "d": 1
    },
    "p3zgudnf7tzqvt50g7lpr2ryno7yugmy": {
    "b": [
    "d10e5600d11e5517"
    ],
    "c": 1,
    "d": 1,
    "e": 1,
    "g": 1,
    "h": [
    "d10e5600d11e5517",
    "d10e5615d11e5527",
    "d10e5605d11e5520",
    "d10e5610d11e5523",
    "d10e5620d11e5530"
    ],
    "q": "a_z6smu56gstysjpqbzp21ruxii6g2ph00"
    },
    "33qfthhugr36f5ts4251glpqx0o373pe": {
    "b": [
    "d10e5633d11e5536"
    ],
    "c": 1,
    "d": 1,
    "e": 1,
    "g": 1,
    "h": [
    "d10e5638d11e5539",
    "d10e5633d11e5536",
    "d10e5643d11e5542",
    "d10e5653d11e5549",
    "d10e5648d11e5546"
    ],
    "q": "a_cydo6wu1ds340j3q6qxeig97thocttsp"
    }
    }
    }

我需要从路径中获取值 "c" -> "pep3lpnp1n1ugmex5uevekg5k20wkfq3" -> "b""c" -> "p3zgudnf7tzqvt50g7lpr2ryno7yugmy" -> "b""c" -> "33qfthhugr36f5ts4251glpqx0o373pe" -> "b",依此类推,其中"pep3lpnp1n1ugmex5uevekg5k20wkfq3"是动态的,并且对于每个JSON输入都会更改。

输出应类似于Seq(object(q,b,c))。

2 个答案:

答案 0 :(得分:1)

如果您不需要知道哪个生成的密钥属于哪个值,则可以使用recursive path \\运算符:

import play.api.libs.json.Json
import play.api.libs.json._

val jsonText = """{
   "a":1540554574847,
   "b":2,
   "c":{
      "onegeneratedkey":{
         "a":1,
         "b":1,
         "c":1,
         "d":1
      },
      "secondsonegeneratedkey":{
         "a":1,
         "b": [1, 2, 3],
         "c":1,
         "d":1
      }
   }
}"""

val result: Seq[JsValue] = Json.parse(jsonText) \ "c" \\ "b"
// res: List(1, [1,2,3])

UPD。

要使用生成键获取存储在对象内部的所有值,可以使用JsObject#values

val valuesSeq: Seq[JsValue] = (Json.parse(jsonText) \ "c").toOption // get 'c' field
  .collect {case o: JsObject => o.values.toSeq} // get all object that corresponds to generated keys
  .getOrElse(Seq.empty)
// res: Seq({"a":1,"b":1,"c":1,"d":1}, {"a":1,"b":[1,2,3],"c":1,"d":1})

val valuesABC = valuesSeq.map(it => (it \ "a", it \ "b", it \ "c"))
// res: Seq((JsDefined(1),JsDefined(1),JsDefined(1)), (JsDefined(1),JsDefined([1,2,3]),JsDefined(1)))

答案 1 :(得分:0)

我误解了问题,这是修改后的版本。

在这里,我使用json.pick读取JsObject并从那里迭代密钥。

Ps:您不必创建Reads或case类,但是它应该使调用程序更具可读性。

import play.api.libs.json.Json
import play.api.libs.json._

val jsonText =
  """{
    "top": {
      "level2a": {
        "a": 1,
        "b": 1,
        "c": 1,
        "d": 1
      },
      "level2b": {
        "a": 2,
        "b": 2,
        "nested": {
          "b": "not interested"
        }
      }
    }
  }"""

case class Data(k: String, v: Int)
case class Datas(list: Seq[Data])

object Datas {
  implicit val reads: Reads[Datas] = (__ \ "top").json.pick.map {
    case obj: JsObject =>
      new Datas(obj.keys.flatMap(k => (obj \ k \ "b").validate[Int] match {
        case JsSuccess(v, _) => Some(Data(k, v))
        case _ => None
      }).toSeq)
  }
}

Json.parse(jsonText).validate[Datas].asOpt match {
  case Some(d) => println(s"found: $d")
  case _ => println("not found")
}

要反序列化level2中的内部结构,可以选择创建内部结构,并使用Json.reads创建默认的读取。只要数据结构是已知且可预测的。

例如

case class Internal(a: Int, b: Int, c: Option[Int], d: Option[Int])
object Internal {
  implicit val reads = Json.reads[Internal]
}
case class Data(k: String, v: Internal)
case class Datas(list: Seq[Data])
object Datas {
  implicit val reads: Reads[Datas] = (__ \ "top").json.pick.map {
    case obj: JsObject =>
      new Datas(obj.keys.flatMap(k => (obj \ k).validate[Internal].asOpt
        .map(v => Data(k, v))).toSeq)

  }
}