1

イベントの配列を含む JSON 構造があります。Aこの配列は、次の 3 つのイベント タイプが考えられるという意味で「多態的」BですC

{
 ...
 "events": [
   { "eventType": "A", ...},
   { "eventType": "B", ...},
   { "eventType": "C", ...},
   ...
 ]
}

3 つのイベント タイプのオブジェクト構造は同じではないReadsため、それぞれに異なるオブジェクト構造が必要です。それとは別に、JSON ドキュメント全体のターゲット ケース クラスは、イベントを区別します。

case class Doc(
   ...,
   aEvents: Seq[EventA],
   bEvents: Seq[EventB],
   cEvents: Seq[EventC],
   ...
)

Reads[Doc]の内部を定義して、json 配列が、およびeventsにマップされる 3 つのサブセットに分割されるようにするにはどうすればよいですか?aEventsbEventscEvents


私がこれまでに試したこと(成功せずに):

最初に、オリジナルを、特定のタイプのイベントのみを含む別のイベントReads[JsArray]に変換するを定義しました。JsArrayJsArray

 def eventReads(eventTypeName: String) = new Reads[JsArray] {
    override def reads(json: JsValue): JsResult[JsArray] = json match {
      case JsArray(seq) =>
        val filtered = seq.filter { jsVal =>
          (jsVal \ "eventType").asOpt[String].contains(eventTypeName)
        }
        JsSuccess(JsArray(filtered))
      case _ => JsError("Must be an array")
    }
  }

次に、アイデアは次のように使用することですReads[Doc]

implicit val docReads: Reads[Doc] = (
    ...
    (__ \ "events").read[JsArray](eventReads("A")).andThen... and
    (__ \ "events").read[JsArray](eventReads("B")).andThen... and
    (__ \ "events").read[JsArray](eventReads("C")).andThen... and
    ...
)(Doc.apply _)

とはいえ、ここから先はどうなるかわかりません。パーツは次のandThenようになるはずです(イベントaの場合):

.andThen[Seq[EventA]](EventA.reads)

しかし、API が の代わりにaSeq[EventA]を明示的に渡すことによってを作成することを期待しているため、それは機能しません。それとは別に、私はそれを実行したことがないので、そもそもこのアプローチ全体が合理的かどうかはわかりません.Reads[EventA]Reads[Seq[EventA]]

編集:オリジナルJsArrayに不明なイベント タイプ (例:DおよびE) が含まれている場合、これらのタイプは無視され、最終結果から除外されます (全体Readsが失敗するのではなく)。

4

2 に答える 2

1

read次のようなすべてのEventタイプに対して暗黙的に置きます

def eventRead[A](et: String, er: Reads[A]) = (__ \ "eventType").read[String].filter(_ == et).andKeep(er)

implicit val eventARead = eventRead("A", Json.reads[EventA])
implicit val eventBRead = eventRead("B", Json.reads[EventB])
implicit val eventCRead = eventRead("C", Json.reads[EventC])

Reads[Doc] を使用します (イベント リストを折りたたみ、シーケンスをタイプ別に分離し、結果を に適用しますDoc):

Reads[Doc] = (__ \ "events").read[List[JsValue]].map(
    _.foldLeft[JsResult[ (Seq[EventA], Seq[EventB], Seq[EventC]) ]]( JsSuccess( (Seq.empty[EventA], Seq.empty[EventB], Seq.empty[EventC]) ) ){
      case (JsSuccess(a, _), v) => 
        (v.validate[EventA].map(e => a.copy(_1 = e +: a._1)) or v.validate[EventB].map(e => a.copy(_2 = e +: a._2)) or v.validate[EventC].map(e => a.copy(_3 = e +: a._3)))      
      case (e, _) => e
    }  
  ).flatMap(p => Reads[Doc]{js => p.map(Doc.tupled)})

イベントリストを介した1回のパスでDocを作成します

JsSuccess(Doc(List(EventA(a)),List(EventB(b2), EventB(b1)),List(EventC(c))),)

ソースデータ

val json = Json.parse("""{"events": [
                        |   { "eventType": "A", "e": "a"},
                        |   { "eventType": "B", "ev": "b1"},
                        |   { "eventType": "C", "event": "c"},
                        |   { "eventType": "B", "ev": "b2"}
                        | ]
                        |}
                        |""")
case class EventA(e: String)
case class EventB(ev: String)
case class EventC(event: String)
于 2016-10-26T20:41:10.310 に答える