playframework - Play JSON Reads[T]: split a JsArray into multiple subsets -
i have json structure contains array of events. array "polymorphic" in sense there 3 possible event types a
, b
, c
:
{ ... "events": [ { "eventtype": "a", ...}, { "eventtype": "b", ...}, { "eventtype": "c", ...}, ... ] }
the 3 event types don't have same object structure, need different reads
them. , apart that, target case class of whole json document distinguishes between events:
case class doc( ..., aevents: seq[eventa], bevents: seq[eventb], cevents: seq[eventc], ... )
how can define internals of reads[doc]
json array events
split 3 subsets mapped aevents
, bevents
, cevents
?
what tried far (without being succesful):
first, defined reads[jsarray]
transform original jsarray
jsarray
contains events of particular type:
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 array") } }
then idea use within reads[doc]
:
implicit val docreads: reads[doc] = ( ... (__ \ "events").read[jsarray](eventreads("a")).andthen... , (__ \ "events").read[jsarray](eventreads("b")).andthen... , (__ \ "events").read[jsarray](eventreads("c")).andthen... , ... )(doc.apply _)
however, don't know how go on here. assume andthen
part should (in case of event a):
.andthen[seq[eventa]](eventa.reads)
but doesn't work since expect api create seq[eventa]
explicitly passing reads[eventa]
instead of reads[seq[eventa]]
. , apart that, since i've never got running, i'm not sure if whole approach reasonable in first place.
edit: in case original jsarray
contains unknown event types (e.g. d
, e
), these types should ignored , left out final result (instead of making whole reads
fail).
put implicit read
every event
type like
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])
and use reads[doc] (folding event list separate sequences types , apply result 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)})
it create doc in 1 pass through events list
jssuccess(doc(list(eventa(a)),list(eventb(b2), eventb(b1)),list(eventc(c))),)
the source data
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)
Comments
Post a Comment