2016-10-26 2 views
1

이벤트 배열이 포함 된 JSON 구조가 있습니다. 배열은 세 가지 이벤트 유형 A, BC이 있다는 의미에서 "다형성"입니다 : 같은 객체 구조가없는JSON 읽기 [T] 읽기 : JsArray를 여러 개의 하위 집합으로 분할

{ 
... 
"events": [ 
    { "eventType": "A", ...}, 
    { "eventType": "B", ...}, 
    { "eventType": "C", ...}, 
    ... 
] 
} 

세 가지 이벤트 유형, 그래서에 대한 Reads 다른 필요 그들. 그리고 그 외에도에서, 전체 JSON 문서의 목표 케이스 클래스는 이벤트를 구별 :

case class Doc(
    ..., 
    aEvents: Seq[EventA], 
    bEvents: Seq[EventB], 
    cEvents: Seq[EventC], 
    ... 
) 

json으로 배열 eventsaEvents에 매핑되는 세 개의 부분 집합으로 분할 될 수 있도록 내가 Reads[Doc]의 내부를 정의 할 수있는 방법 , bEventscEvents? 나는 (성공적인없이) 지금까지 시도 무엇


:

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") 
    } 
    } 
:

먼저, 난 단지 특정 유형의 이벤트가 포함 된 다른 JsArray 원래 JsArray 변환하는 Reads[JsArray] 정의

다음과 같이 이것을 사용하면됩니다. 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[Seq[EventA]]Reads[EventA]를 전달하여 Seq[EventA]를 만들 것으로 기대하기 때문에 그 작동하지 않습니다. 그리고 그 점을 제외하고는 결코 실행하지 못했기 때문에이 모든 접근법이 처음에는 합리적인지 확실하지 않습니다.

편집 : 경우에 원래 JsArray 알 수없는 이벤트 유형 (예를 들어, DE)를 포함은, 이러한 유형은 무시되어야하고 (대신 전체 Reads 실패 만드는) 최종 결과에서 탈락.

+0

json 구조를 변경할 수 있습니까? 예 : '{ ..., 이벤트 : { a : [... A 이벤트 유형], b : [B 유형 이벤트 ...] c : [...의 이벤트 C] } } 이미 정의한 읽기를 사용하여 다시 변환하는 것이 더 쉬워야합니다. – marceloemanoel

+0

다른 방법. JsArray를 Tuple3 [Seq [EventA]], Seq [EventB], Seq [EventC]로 변환하고 결과를 Doc에 매핑하십시오. –

답변

1

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

같은 모든 Event 유형 암시 read 넣어 사용 [문서는 (접는 이벤트리스트 형식으로 시퀀스를 분리 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)}) 

는 것 이벤트 목록을 통해 한 번에 문서 만들기

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 

그러면 모든 이벤트를 단일 컬렉션에 저장하고 나중에 패턴 일치를 사용하여 수정할 수 있습니다. 예를 들면 다음과 같습니다.

읽기를 구현하려면 문서가 자연스럽게 사례 클래스에 매핑되고 이벤트에 대한 매핑 만 제공하면됩니다. 당신은 다음과 같이 사용할 수 있습니다

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