播放JSON读取[T]:将JsArray拆分为多个子集

时间:2016-10-26 10:53:21

标签: json playframework play-json

我有一个包含一系列事件的JSON结构。该阵列是多态的"在某种意义上,有三种可能的事件类型ABC

{
 ...
 "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拆分为三个子集,这些子集映射到aEventsbEvents和{{1} }?

到目前为止我尝试了什么(没有成功):

首先,我定义了cEvents将原始Reads[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 _) 部分应该看起来像这样(在事件a的情况下):

andThen

但是,由于我希望API通过显式传递.andThen[Seq[EventA]](EventA.reads) 而不是Seq[EventA]来创建Reads[EventA],因此无法正常工作。除此之外,由于我从来没有让它运行,我不确定这一整个方法是否合理。

修改:如果原始Reads[Seq[EventA]]包含未知事件类型(例如JsArrayD),则应忽略这些类型,并将其从最终结果(而不是使整个E失败)。

2 个答案:

答案 0 :(得分: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())

为了实现您的Read,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())

希望这有帮助。

答案 1 :(得分: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)})

它将在一次通过事件列表中创建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)