1

我有一个包含事件数组的 JSON 结构。该数组是“多态的”,因为存在三种可能的事件类型AB并且C

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

这三种事件类型没有相同的对象结构,所以我需要不同Reads的。除此之外,整个 JSON 文档的目标案例类区分事件:

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

如何定义内部结构,Reads[Doc]以便将 json 数组events拆分为三个子集,这些子集映射到aEvents和?bEventscEvents


到目前为止我尝试过的(没有成功):

首先,我定义了 aReads[JsArray]将原始转换为仅包含特定类型事件的JsArray另一个:JsArray

 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)

但这不起作用,因为我希望 APISeq[EventA]通过显式传递 aReads[EventA]而不是Reads[Seq[EventA]]. 除此之外,由于我从未运行过它,所以我不确定整个方法一开始是否合理。

编辑:如果原始JsArray包含未知的事件类型(例如DE),这些类型应该被忽略并从最终结果中排除(而不是使整个Reads失败)。

4

2 回答 2

1

我将模拟您将不同事件类型存储在 JS 数组中作为类层次结构的事实,以确保其类型安全。

sealed abstract class Event
case class EventA() extends Event
case class EventB() extends Event
case class EventC() extends Event

然后,您可以将所有事件存储在一个集合中,并稍后使用模式匹配来优化它们。例如:

case class Doc(events: Seq[Event]) {
    def getEventsA: Seq[EventA] = events.flatMap(_ match {
        case e: EventA => Some(e)
        case _ => None
    })
}

Doc(Seq(EventA(), EventB(), EventC())).getEventsA // res0: Seq[EventA] = List(EventA())

为了实现你的Reads,Doc自然会映射到case类,你只需要提供一个Event的映射即可。这是它的样子:

implicit val eventReads = new Reads[Event] {
    override def reads(json: JsValue): JsResult[Event] = json \ "eventType" match {
        case JsDefined(JsString("A")) => JsSuccess(EventA())
        case JsDefined(JsString("B")) => JsSuccess(EventB())
        case JsDefined(JsString("C")) => JsSuccess(EventC())
        case _ => JsError("???")
    }
}
implicit val docReads = Json.reads[Doc]

然后你可以像这样使用它:

val jsValue = Json.parse("""
 {
  "events": [
    { "eventType": "A"},
    { "eventType": "B"},
    { "eventType": "C"}
  ]
 }
""")
val docJsResults = docReads.reads(jsValue) // docJsResults: play.api.libs.json.JsResult[Doc] = JsSuccess(Doc(List(EventA(), EventB(), EventC())),/events)

docJsResults.get.events.length // res1: Int = 3
docJsResults.get.getEventsA // res2: Seq[EventA] = List(EventA())

希望这可以帮助。

于 2016-10-26T15:42:15.430 回答
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)})

它将通过事件列表一次性创建文档

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 回答