2016-10-26 2 views
1

У меня есть структура JSON, которая содержит массив событий. Массив «полиморфный» в том смысле, что существует три возможных типа событий A, B и C:Воспроизведение JSON Reads [T]: разбить JsArray на несколько подмножеств

{ 
... 
"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 и 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 часть должна выглядеть примерно так (в случае событий а):

.andThen[Seq[EventA]](EventA.reads) 

Но это не работает, так как я ожидаю, что API для создания Seq[EventA], явно передавая Reads[EventA] вместо Reads[Seq[EventA]]. И кроме того, поскольку я никогда не запускал его, я не уверен, что в целом этот подход разумный.

редактировать: в случае, если оригинал JsArray содержит неизвестные типы событий (например, D и E), эти типы должны быть проигнорированы и исключены из окончательного результата (вместо того, чтобы весь Reads неудачу).

+0

Можете вы изменить структуру json? Что-то вроде '{ ... события: { : с [... события типа A], б: [... события типа B], с: [... события типа C] } } ' Это должно быть проще, чтобы преобразовать их обратно с помощью считываний, которые вы уже определили. – marceloemanoel

+0

Другой способ. Преобразовать «события» JsArray в Tuple3 [Seq [EventA]], Seq [EventB]], Seq [EventC]], а затем отобразить результат в Doc. –

ответ

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]) 

и использование Считывает [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) 
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()) 

Для реализации вашего Считывает, Doc будет, естественно, переходит в класс случае, вам нужно только обеспечить отображение для случая. Вот то, что она может выглядеть следующим образом:

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

Ваш 'getEventsA' проще:' events.collect {case a: EventA => a} ' – fxlae

+0

Другая возможность -' events.filter {_. IsInstanceOf [EventA]} '. – fxlae

 Смежные вопросы

  • Нет связанных вопросов^_^