This is a fairly common question. This answer inspired by Reads.traversableReads[F[_], A]
.
To support the idea of cumulative of Reads[A]
, we must try all your genererated Reads[JsObject]
and we will use Either[Errors, Vector[JsObject]] for this. In original 'Reads.traversableReads[F[_], A]' returns Reads[List[A]]
or some collection, but we need simple Json, no problem, ++
concatinates our JsObjects
.
def reduceReads(generated: Seq[Reads[JsObject]]) = Reads {json =>
type Errors = Seq[(JsPath, Seq[ValidationError])]
def locate(e: Errors, idx: Int) = e.map { case (p, valerr) => (JsPath(idx)) ++ p -> valerr }
generated.iterator.zipWithIndex.foldLeft(Right(Vector.empty): Either[Errors, Vector[JsObject]]) {
case (acc, (r, idx)) => (acc, r.reads(json)) match {
case (Right(vs), JsSuccess(v, _)) => Right(vs :+ v)
case (Right(_), JsError(e)) => Left(locate(e, idx))
case (Left(e), _: JsSuccess[_]) => Left(e)
case (Left(e1), JsError(e2)) => Left(e1 ++ locate(e2, idx))
}
}
.fold(JsError.apply, { res =>
JsSuccess(res.fold(Json.obj())(_ ++ _))
})
}
scala> json: play.api.libs.json.JsValue = {"attr1":{"attr1a":"attr1a"},"attr2":"attr2"}
scala> res7: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"attr1":"attr1a","attr2":"attr2"},)
new awesome simple answer
After few days I had this brilliant idea. object Reads
have implicit Reducer[JsObject, JsObject]
so we can reduce Seq(Reads[JsObject])
with FunctionalBuilder
(and
and then reduce
)!
def reduceReads(generated: Seq[Reads[JsObject]]) =
generated.foldLeft(Reads.pure(Json.obj())){
case (acc, r) =>
(acc and r).reduce
}
This solution is simple and clear. Original idea based Seq(Reads[JsObject]) => Seq(JsResult[JsObject]) => Reads[JsObject]
mapping, but the last based on basic Json combinators principles Seq(Reads[JsObject]) => Reads[JsObject]
In general, the problem is solved, but the task itself is not correct. If you don't control Reads, what you want see if the same path will be used twice?