2

我们正在使用 Play 框架 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
                 }
               ]
             }
           }
         ]
       }
     }
   ]
  }
}

我们不知道响应结构会是什么样子,但我们唯一知道的是响应中的某处会有“桶”嵌套元素,并且您可以看到顶层内还有其他嵌套的“桶”“桶”元素。另请注意,buckets数组内部的结构也不清楚,如果会有另一个子存储桶,则肯定子存储桶必须位于父存储桶内的某个bucket位置 - 这样模式是一致的。

解析这种递归结构并递归填充以下Bucket类的最佳方法是什么?

case class Bucket(key:String,docCount, subBuckets: List[Bucket] ) 

首先我想

val json = Json.parse(serviveResponse)
val buckets = (json \ "response" \\ "buckets") 

但这不会带来buckets递归而不是正确的遍历方式。

有任何想法吗?

4

2 回答 2

1

Reads[T]为递归类型T制作 a ,您必须

  • 将其定义为lazy val,
  • lazyRead在需要递归解析的地方使用,
  • 并手动将Reads[T]对象或其衍生物传递给它。

当然,您必须知道buckets元素可能出现在哪些路径上,并且还要考虑到它在任何路径中的缺失。您可以使用orElse多条路径进行尝试。

对于您的 定义BucketReads可能如下所示:

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]],或者以某种方式组合现有的 monoid 实例JsArray

于 2016-02-23T18:56:05.553 回答
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
}
于 2016-02-23T16:54:20.873 回答