I am attempting to make an API for stripe which involves a lot of mapping from Json to case classes (and vice versa). I have come across an issue where I end up with a List[JsResult[A]]
(this is the result of mapping through a list of JObject's and doing some operations on them to map them to the appropriate case class). The code in question is below
case class Sources(data: List[PaymentSource],
hasMore: Boolean,
totalCount: Double,
url: String)
implicit val sourcesReader: Reads[Sources] = {
val dataAsList = (__ \ "data").read[List[JsObject]].flatMap{jsObjects =>
val `jsResults` = jsObjects.map{jsObject =>
val `type` = jsObject \ "type"
val paymentSource: JsResult[PaymentSource] = `type` match {
case JsString("card") =>
Json.fromJson[Card](jsObject)
case JsString("bitcoin_receiver") =>
Json.fromJson[BitcoinReceiver](jsObject)
case JsString(s) =>
throw UnknownPaymentSource(s)
case _ =>
throw new IllegalArgumentException("Expected a Json Object")
}
paymentSource
}
jsResults
}
The jsResults has a type of List[JsResult[A]]
, however to compose it properly with the reads we need to return either a JsResult[A]
or a JsError
.
Although its possible to do Json.fromJson[Card](jsObject).get
instead of Json.fromJson[Card](jsObject)
, doing so means we lose the accumulative error handling in Play Json (it also means we are pushing the errors into runtime)
So, you can't turn a List[JsResult[A]]
into JsResult[A]
, because what if you have multiple success results? That would mean you have multiple values for A
. You can turn it into JsResult[List[A]]
, there are a few ways to do this, I'd probably do this:
val allErrors = jsResults.collect {
case JsError(errors) => errors
}.flatten
val jsResult = if (allErrors.nonEmpty) {
JsError(allErrors)
} else {
JsSuccess(jsResults.collect {
case JsSuccess(a, _) => a
})
}