diff --git a/play-json/shared/src/main/scala/play/api/libs/json/Reads.scala b/play-json/shared/src/main/scala/play/api/libs/json/Reads.scala index 3cd3c6bcd..09c18b591 100644 --- a/play-json/shared/src/main/scala/play/api/libs/json/Reads.scala +++ b/play-json/shared/src/main/scala/play/api/libs/json/Reads.scala @@ -121,6 +121,31 @@ trait Reads[A] { self => this.reads(json) } + /** + * Creates a new `Reads`, which transforms the successful result + * from the current instance using the given function. + * + * @param f the function applied on the successful `A` value + * + * {{{ + * final class Foo private(val code: String) extends AnyVal + * + * val A = new Foo("A") + * val B = new Foo("B") + * + * import play.api.libs.json.Reads + * + * val r: Reads[Foo] = implicitly[Reads[String]].flatMapResult { + * case "A" => JsSuccess(A) + * case "B" => JsSuccess(B) + * case _ => JsError("error.expected.foo") + * } + * }}} + */ + def flatMapResult[B](f: A => JsResult[B]): Reads[B] = Reads[B] { + this.reads(_).flatMap(f) + } + def andThen[B](rb: Reads[B])(implicit witness: A <:< JsValue): Reads[B] = rb.composeWith(this.map(witness)) diff --git a/play-json/shared/src/test/scala/play/api/libs/json/ReadsSharedSpec.scala b/play-json/shared/src/test/scala/play/api/libs/json/ReadsSharedSpec.scala index 63fff8130..5cf19767a 100644 --- a/play-json/shared/src/test/scala/play/api/libs/json/ReadsSharedSpec.scala +++ b/play-json/shared/src/test/scala/play/api/libs/json/ReadsSharedSpec.scala @@ -119,6 +119,23 @@ final class ReadsSharedSpec extends AnyWordSpec with Matchers with Inside { } } + "Reads result" should { + "be flat-mapped" in { + val readsArrayAsOwner: Reads[Owner] = + Reads.seq[String].flatMapResult { + case login +: avatar +: url +: _ => + JsSuccess(Owner(login, avatar, url)) + + case _ => + JsError("error.expected.owner-as-jsarray") + } + + readsArrayAsOwner.reads(JsArray(Seq(JsString("foo"), JsString("bar"), JsString("url://owner")))) mustEqual JsSuccess( + Owner("foo", "bar", "url://owner") + ) + } + } + "Functional Reads" should { import play.api.libs.functional.syntax._