Play JSON Reads[T]: 将一个 JsArray 拆分成多个子集
Play JSON Reads[T]: split a JsArray into multiple subsets
我有一个包含事件数组的 JSON 结构。该数组是 "polymorphic",因为存在三种可能的事件类型 A
、B
和 C
:
{
...
"events": [
{ "eventType": "A", ...},
{ "eventType": "B", ...},
{ "eventType": "C", ...},
...
]
}
三种事件类型没有相同的对象结构,所以我需要不同的Reads
。除此之外,整个JSON文档的目标案例class区分了事件:
case class Doc(
...,
aEvents: Seq[EventA],
bEvents: Seq[EventB],
cEvents: Seq[EventC],
...
)
如何定义 Reads[Doc]
的内部结构,以便 json 数组 events
分成三个子集,分别映射到 aEvents
、bEvents
和 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 _)
但是,我不知道如何从这里继续下去。我假设 andThen
部分应该看起来像这样(在事件 a 的情况下):
.andThen[Seq[EventA]](EventA.reads)
但这不起作用,因为我希望 API 通过显式传递 Reads[EventA]
而不是 Reads[Seq[EventA]]
来创建 Seq[EventA]
。除此之外,因为我从来没有得到它 运行,我不确定这整个方法是否合理。
编辑: 如果原始 JsArray
包含未知事件类型(例如 D
和 E
),则应忽略这些类型并从最终结果中排除(而不是使整个 Reads
失败)。
我会模拟这样一个事实,即您将不同的事件类型存储在 JS 数组中作为 class 层次结构以确保其类型安全。
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上class,你只需要提供一个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())
希望这对您有所帮助。
为每个 Event
类型输入隐式 read
,例如
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)
我有一个包含事件数组的 JSON 结构。该数组是 "polymorphic",因为存在三种可能的事件类型 A
、B
和 C
:
{
...
"events": [
{ "eventType": "A", ...},
{ "eventType": "B", ...},
{ "eventType": "C", ...},
...
]
}
三种事件类型没有相同的对象结构,所以我需要不同的Reads
。除此之外,整个JSON文档的目标案例class区分了事件:
case class Doc(
...,
aEvents: Seq[EventA],
bEvents: Seq[EventB],
cEvents: Seq[EventC],
...
)
如何定义 Reads[Doc]
的内部结构,以便 json 数组 events
分成三个子集,分别映射到 aEvents
、bEvents
和 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 _)
但是,我不知道如何从这里继续下去。我假设 andThen
部分应该看起来像这样(在事件 a 的情况下):
.andThen[Seq[EventA]](EventA.reads)
但这不起作用,因为我希望 API 通过显式传递 Reads[EventA]
而不是 Reads[Seq[EventA]]
来创建 Seq[EventA]
。除此之外,因为我从来没有得到它 运行,我不确定这整个方法是否合理。
编辑: 如果原始 JsArray
包含未知事件类型(例如 D
和 E
),则应忽略这些类型并从最终结果中排除(而不是使整个 Reads
失败)。
我会模拟这样一个事实,即您将不同的事件类型存储在 JS 数组中作为 class 层次结构以确保其类型安全。
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上class,你只需要提供一个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())
希望这对您有所帮助。
为每个 Event
类型输入隐式 read
,例如
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)