2

Play フレームワーク 2.3.4 を使用しています。API の 1 つからサードパーティ サービスへの 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にするには、次のようにする必要があります。

  • として定義し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この例では、1 つのバケット内の異なるパスにある複数のアレイがマージされる可能性を考慮していません。play.api.libs.functional.Monoidしたがって、その機能が必要な場合は、 のインスタンスを定義して使用するReads[List[T]]か、 の既存のモノイド インスタンスを何らかの形で組み合わせる必要があると思います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 に答える