我们正在使用Play framework 2.3.4。从其中一个API我们对第三方服务进行Web服务调用 - 返回响应的结构是动态的,可能会发生变化。只有在响应JSON中静态的子结构才是特定元素并嵌套在其中。例如。
{
"response":
{
"someElement1": "",
"element2": true,
"buckets": [
{
"key": "keyvalue",
"docCount": 10,
"somethingElse": {
"buckets": [
{
"key": "keyvalue1",
"docCount": 5,
"somethingElseChild": {
"buckets": [
{
"key": "Tan",
"docCount": 1
}
]
}
},
{
"key": "keyvalue2",
"docCount": 3,
"somethingElseChild": {
"buckets": [
{
"key": "Ban",
"docCount": 6
}
]
}
}
]
}
}
]
}
}
我们不知道响应结构会是什么样子,但我们知道的唯一事情就是会有"桶和#34;嵌套元素在响应中的某个位置,你可以看到还有其他嵌套的#34;桶#34;在顶级水平"水桶"元件。另请注意,buckets
数组中的结构也不清楚,如果有另一个子存储桶,则确定子存储桶必须位于父bucket
内的某个位置 - 这样模式是一致的。
什么是解析这种递归结构并以递归方式填充Bucket
类后的最佳方法?
case class Bucket(key:String,docCount, subBuckets: List[Bucket] )
首先我想
val json = Json.parse(serviveResponse)
val buckets = (json \ "response" \\ "buckets")
但这不会带来buckets
递归而不是正确的遍历方式。
有什么想法吗?
答案 0 :(得分:1)
要制作Reads[T]
for a recursive type T
,您必须
lazy val
,lazyRead
,Reads[T]
对象或其衍生物。当然,您必须知道buckets
元素可能出现在哪些路径上,并且还要考虑其中的任何路径。您可以使用orElse
尝试多条路径。
对于Bucket
的定义,Reads
可能如下所示:
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit lazy val readBucket: Reads[Bucket] = (
(__ \ "key").read[String] and
(__ \ "docCount").read[Int] and
(
(__ \ "somethingElse" \ "buckets").lazyRead(Reads.list(readBucket)) orElse
(__ \ "somethingElseChild" \ "buckets").lazyRead(Reads.list(readBucket)) orElse
Reads.pure(List.empty[Bucket])
)
) (Bucket.apply _)
您可以通过将公共部分提取到函数来简化它,例如:
def readsBucketsAt(path: JsPath): Reads[List[Bucket]] =
(path \ "buckets").lazyRead(Reads.list(readBucket))
/* ...
readsBucketsAt(__ \ "somethingElse") orElse
readsBucketsAt(__ \ "somethingElseChild") orElse
Reads.pure(List.empty[Bucket])
... */
此示例并未考虑在单个存储桶内的不同路径上可能合并多个buckets
数组。因此,如果您需要该功能,我相信您必须为play.api.libs.functional.Monoid
定义和使用Reads[List[T]]
实例,或以某种方式合并JsArray
的现有monoid实例。
答案 1 :(得分:0)
递归解析。这样的事情(未经测试):
case class Bucket(key: String, count: Int, sub: List[Bucket])
def FAIL = throw new Exception // Put better error-handling here
def getBucket(js: JsValue): Bucket = js match {
case o: JsObject =>
val key = (o \ "key") match {
case JsString(s) => s
case _ => FAIL
}
val count = (o \ "docCount") match {
case JsNumber(n) => n.toInt
case _ => FAIL
}
val sub = (o \ "buckets") match {
case a: JsArray => a.value.toList.map(getBucket)
case _ => Nil
}
Bucket(key, count, sub)
case _ => throw new Exception
}