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)
You may use Reads.list()
.
val paymentSourceReader: Reads[PaymentSource] = __.read[JsObject].flatMap { o =>
(__ \ "type").read[String].collect(ValidationError("UnknownPaymentSource")) {
case "card" =>
o.as[Card]
case "bitcoin_receiver" =>
o.as[BitcoinReceiver]
}
}
read[String]
create error if no type
pproperty. collect(ValidationError("UnknownPaymentSource")
create error if type !(card|bitcoin_receiver). o.as[...]
throw exception if can not cast then use `paymentSourceReader'
val dataReader: Reads[List[PaymentSource]] = (__ \ "data").read[List[PaymentSource]](Reads.list(paymentSourceReader))
dataReader
may be used in complex reader Reads[PaymentSource]
with combinators for Sources
or json.reads(dataReader)
for JsResult[List[PaymentSource]]
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
})
}
I had a similar problem. I wanted to join two JsResults into on JsSuccess. tupled
and and
helped me to fulfill this task. Both are part of the play.api.libs.functional
package.
This is how you can join two JsResult
into one:
import play.api.libs.json._
import play.api.libs.functional.syntax._
((__ \ 'id).validate[Long] and (__ \ 'name).validate[String]).tupled
match {
case JsSuccess((id,name),_) => ...
case err: JsError =>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.