Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

trait RecursiveFormat { self: Format.type =>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

trait RecursiveOFormat { self: OFormat.type =>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

trait RecursiveOWrites { self: OWrites.type =>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

trait RecursiveReads { self: Reads.type =>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

trait RecursiveWrites { self: Writes.type =>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

import scala.annotation.tailrec

trait RecursiveFormat { self: Format.type =>
final def recursive[A](f: Format[A] ?=> Format[A]): Format[A] = {
lazy val res: Format[A] = f(using RecursiveFormat.DeferredFormat(() => res))
res
}
}

private[json] object RecursiveFormat {
private final case class DeferredFormat[A](value: () => Format[A]) extends Format[A] {
private lazy val resolved: Format[A] = resolve(value)

@tailrec
private def resolve(f: () => Format[A]): Format[A] =
f() match {
case DeferredFormat(f) =>
resolve(f)
case next =>
next
}

override def reads(json: JsValue): JsResult[A] =
resolved.reads(json)

override def writes(o: A): JsValue =
resolved.writes(o)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

import scala.annotation.tailrec

trait RecursiveOFormat { self: OFormat.type =>
final def recursive[A](f: OFormat[A] ?=> OFormat[A]): OFormat[A] = {
lazy val res: OFormat[A] = f(using RecursiveOFormat.DeferredOFormat(() => res))
res
}
}

private[json] object RecursiveOFormat {
private final case class DeferredOFormat[A](value: () => OFormat[A]) extends OFormat[A] {
private lazy val resolved: OFormat[A] = resolve(value)

@tailrec
private def resolve(f: () => OFormat[A]): OFormat[A] =
f() match {
case DeferredOFormat(f) =>
resolve(f)
case next =>
next
}

override def reads(json: JsValue): JsResult[A] =
resolved.reads(json)

override def writes(o: A): JsObject =
resolved.writes(o)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

import scala.annotation.tailrec

trait RecursiveOWrites { self: OWrites.type =>
final def recursive[A](f: OWrites[A] ?=> OWrites[A]): OWrites[A] = {
lazy val res: OWrites[A] = f(using RecursiveOWrites.DeferredOWrites(() => res))
res
}
}

private[json] object RecursiveOWrites {
private final case class DeferredOWrites[A](value: () => OWrites[A]) extends OWrites[A] {
private lazy val resolved: OWrites[A] = resolve(value)

@tailrec
private def resolve(f: () => OWrites[A]): OWrites[A] =
f() match {
case DeferredOWrites(f) =>
resolve(f)
case next =>
next
}

override def writes(o: A): JsObject =
resolved.writes(o)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

import scala.annotation.tailrec

trait RecursiveReads { self: Reads.type =>
final def recursive[A](f: Reads[A] ?=> Reads[A]): Reads[A] = {
lazy val res: Reads[A] = f(using RecursiveReads.DeferredReads(() => res))
res
}
}

private[json] object RecursiveReads {
private final case class DeferredReads[A](value: () => Reads[A]) extends Reads[A] {
private lazy val resolved: Reads[A] = resolve(value)

@tailrec
private def resolve(f: () => Reads[A]): Reads[A] =
f() match {
case DeferredReads(f) =>
resolve(f)
case next =>
next
}

override def reads(json: JsValue): JsResult[A] =
resolved.reads(json)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

import scala.annotation.tailrec

trait RecursiveWrites { self: Writes.type =>
final def recursive[A](f: Writes[A] ?=> Writes[A]): Writes[A] = {
lazy val res: Writes[A] = f(using RecursiveWrites.DeferredWrites(() => res))
res
}
}

private[json] object RecursiveWrites {
private final case class DeferredWrites[A](value: () => Writes[A]) extends Writes[A] {
private lazy val resolved: Writes[A] = resolve(value)

@tailrec
private def resolve(f: () => Writes[A]): Writes[A] =
f() match {
case DeferredWrites(f) =>
resolve(f)
case next =>
next
}

override def writes(o: A): JsValue =
resolved.writes(o)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ trait OFormat[A] extends OWrites[A] with Reads[A] with Format[A] {

}

object OFormat {
object OFormat extends RecursiveOFormat {
implicit def functionalCanBuildFormats(implicit
rcb: FunctionalCanBuild[Reads],
wcb: FunctionalCanBuild[OWrites]
Expand Down Expand Up @@ -64,7 +64,7 @@ object OFormat {
/**
* Default Json formatters.
*/
object Format extends PathFormat with ConstraintFormat with DefaultFormat {
object Format extends PathFormat with ConstraintFormat with DefaultFormat with RecursiveFormat {
val constraints: ConstraintFormat = this
val path: PathFormat = this

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ trait Reads[A] { self =>
/**
* Default deserializer type classes.
*/
object Reads extends ConstraintReads with PathReads with DefaultReads with GeneratedReads {
object Reads extends ConstraintReads with PathReads with DefaultReads with GeneratedReads with RecursiveReads {

val constraints: ConstraintReads = this

val path: PathReads = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ trait OWrites[A] extends Writes[A] {
override def narrow[B <: A]: OWrites[B] = this.asInstanceOf[OWrites[B]]
}

object OWrites extends PathWrites with ConstraintWrites {
object OWrites extends PathWrites with ConstraintWrites with RecursiveOWrites {
import play.api.libs.functional._

def of[A](implicit w: OWrites[A]): OWrites[A] = w
Expand Down Expand Up @@ -237,7 +237,7 @@ object OWrites extends PathWrites with ConstraintWrites {
/**
* Default Serializers.
*/
object Writes extends PathWrites with ConstraintWrites with DefaultWrites with GeneratedWrites {
object Writes extends PathWrites with ConstraintWrites with DefaultWrites with GeneratedWrites with RecursiveWrites {
val constraints: ConstraintWrites = this
val path: PathWrites = this

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.api.libs.json

import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json.RecursiveSpec.Foo
import play.api.libs.functional.syntax.*

final class RecursiveSpec extends AnyWordSpec {
"recursive" should {
"Reads" in {
given Reads[Foo] = Reads.recursive(
(
(__ \ "x").read[Int] and
(__ \ "y").read[List[Foo]]
)(Foo.apply)
)
assert(Foo.json1.as[Foo] == Foo.value1)
}
"Writes" in {
given Writes[Foo] = Writes.recursive(
(
(__ \ "x").write[Int] and
(__ \ "y").write[List[Foo]]
)(Tuple.fromProductTyped(_))
)
assert(Json.toJson(Foo.value1) == Foo.json1)
}
"OWrites" in {
given OWrites[Foo] = OWrites.recursive(
(
(__ \ "x").write[Int] and
(__ \ "y").write[List[Foo]]
)(Tuple.fromProductTyped(_))
)
assert(Json.toJson(Foo.value1) == Foo.json1)
}
"Format" in {
given Format[Foo] = Format.recursive(
(
(__ \ "x").format[Int] and
(__ \ "y").format[List[Foo]]
)(Foo.apply, Tuple.fromProductTyped)
)
assert(Json.toJson(Foo.value1) == Foo.json1)
assert(Foo.json1.as[Foo] == Foo.value1)
}
"OFormat" in {
given OFormat[Foo] = OFormat.recursive(
(
(__ \ "x").format[Int] and
(__ \ "y").format[List[Foo]]
)(Foo.apply, Tuple.fromProductTyped)
)
assert(Json.toJson(Foo.value1) == Foo.json1)
assert(Foo.json1.as[Foo] == Foo.value1)
}
}
}

object RecursiveSpec {
private final case class Foo(x: Int, y: List[Foo])

private object Foo {
val value1: Foo = Foo(
1,
List(
Foo(2, Nil),
Foo(3, List(Foo(4, Nil))),
Foo(5, Nil),
Foo(
6,
List(
Foo(7, Nil),
Foo(8, Nil)
)
),
)
)

val json1: JsObject = Json.obj(
"x" -> 1,
"y" -> Json.arr(
Json.obj(
"x" -> 2,
"y" -> Json.arr()
),
Json.obj(
"x" -> 3,
"y" -> Json.arr(
Json.obj(
"x" -> 4,
"y" -> Json.arr(),
)
)
),
Json.obj(
"x" -> 5,
"y" -> Json.arr()
),
Json.obj(
"x" -> 6,
"y" -> Json.arr(
Json.obj(
"x" -> 7,
"y" -> Json.arr(),
),
Json.obj(
"x" -> 8,
"y" -> Json.arr(),
),
)
)
)
)
}
}
Loading