Skip to content

Instantly share code, notes, and snippets.

@steinybot
Created August 19, 2020 04:58
Show Gist options
  • Select an option

  • Save steinybot/9d00d0aa8bf56e472210431b6beaedf5 to your computer and use it in GitHub Desktop.

Select an option

Save steinybot/9d00d0aa8bf56e472210431b6beaedf5 to your computer and use it in GitHub Desktop.
Play JSON Helpers
import play.api.libs.json._
package object json extends JsObjectOps
with JsPathImplicits
with ReadOps {
object formats extends FullDateTimeFormat
with MapFormats
with RefinedFormats
with SingletonFormats
implicit class RichReads[A](val reads: Reads[A]) extends AnyVal {
def flatMapResult[B](f: A => JsResult[B]): Reads[B] = ReadOps.flatMapResult(reads)(f)
// Workaround for https://github.com/playframework/play-json/issues/171
def composeKeepPath[B <: JsValue](rb: Reads[B]): Reads[A] = Reads { js =>
rb.reads(js) match {
case JsSuccess(b, _) => reads.reads(b)
case JsError(e) => JsError(e)
}
}
// Workaround for https://github.com/playframework/play-json/issues/171
def andThenKeepPath[B](rb: Reads[B])(implicit witness: A <:< JsValue): Reads[B] = {
rb.composeKeepPath(reads.map(witness))
}
}
implicit class RichJsObjectReads(val reads: Reads[JsObject]) extends AnyVal {
def orElseEmpty: Reads[JsObject] = reads.orElse(Reads.pure(JsObject.empty))
}
implicit class RichJsArrayReads(val reads: Reads[JsArray]) extends AnyVal {
def orElseEmpty: Reads[JsArray] = reads.orElse(Reads.pure(JsArray.empty))
}
}
import play.api.libs.functional._
import play.api.libs.json.Reads._
import play.api.libs.json._
import scala.collection.GenTraversableOnce
trait ReadOps {
def updateFieldValues[A <: JsValue](reads: Reads[A]): Reads[JsObject] = {
of[JsObject].flatMap { obj =>
reduce(updateObjectFieldValues(obj)(reads))
}
}
private def updateObjectFieldValues[A <: JsValue](obj: JsObject)(reads: Reads[A]): Iterable[Reads[JsObject]] = {
obj.keys.map { key =>
val path = __ \ key
path.json.copyFrom {
path.json.pick andThen __.json.update(reads)
}
}
}
def mapFieldValues[A <: JsValue](reads: Reads[A]): Reads[JsObject] = {
of[JsObject].flatMap { obj =>
reduce(mapObjectFieldValues(obj)(reads))
}
}
def mapFieldValuesWithKey[A <: JsValue](reads: String => Reads[A]): Reads[JsObject] = {
of[JsObject].flatMap { obj =>
reduce(obj.keys.map { key =>
val path = __ \ key
path.json.copyFrom(path.read(reads(key)))
})
}
}
private def mapObjectFieldValues[A <: JsValue](obj: JsObject)(reads: Reads[A]): Iterable[Reads[JsObject]] = {
obj.keys.map { key =>
val path = __ \ key
path.json.copyFrom(path.read(reads))
}
}
def updateItems[A <: JsValue](reads: Reads[A]): Reads[JsArray] = {
of[JsArray].flatMap { obj =>
reduce(updateArrayItems(obj)(reads))
}
}
private def updateArrayItems[A <: JsValue](array: JsArray)(reads: Reads[A]): Iterable[Reads[JsValue]] = {
array.value.indices.map { i =>
(__ \ i).read[JsObject].flatMapResult(_.transform(reads).map[JsValue](identity).repath(__ \ i))
}
}
def mapItems[A <: JsValue](reads: Reads[A]): Reads[JsArray] = {
of[JsArray].flatMap { obj =>
reduce(mapArrayItems(obj)(reads))
}
}
private def mapArrayItems[A <: JsValue](array: JsArray)(reads: Reads[A]): Iterable[Reads[JsValue]] = {
array.value.indices.map { i =>
(__ \ i).read(reads).map[JsValue](identity)
}
}
def flatMapResult[A, B](reads: Reads[A])(f: A => JsResult[B]): Reads[B] = {
reads.flatMap { a =>
Reads { _ =>
f(a)
}
}
}
def reduce[M[_], A, B](mas: GenTraversableOnce[M[A]])
(implicit applicative: Applicative[M],
monoid: Monoid[B],
reducer: Reducer[A, B]): M[B] = {
mas.foldLeft(applicative.pure(monoid.identity)) {
case (mb, ma) => combine(ma, mb)
}
}
private[json] def combine[M[_], A, B](ma: M[A], mb: M[B])
(implicit applicative: Applicative[M], reducer: Reducer[A, B]): M[B] = {
applicative.apply(applicative.map(ma, (a: A) => (b: B) => reducer.prepend(a, b)), mb)
}
}
object ReadOps extends ReadOps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment