我有一个包含一系列事件的JSON结构。该阵列是多态的"在某种意义上,有三种可能的事件类型A
,B
和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
,bEvents
和{{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]]
包含未知事件类型(例如JsArray
和D
),则应忽略这些类型,并将其从最终结果(而不是使整个E
失败)。
答案 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)