Skip to content
Merged
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
23 changes: 23 additions & 0 deletions docs/manual/working/scalaGuide/main/json/ScalaJsonCombinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,29 @@ One special case that our example model doesn't demonstrate is how to handle `Re

@[reads-writes-recursive](code/ScalaJsonCombinatorsSpec.scala)

When defining a recursive codec with a Scala 3 `given`, use the Scala 3-only
`recursive` method instead. It supplies the codec being constructed as a
deferred contextual value, so codecs for recursive fields can be derived
without referring directly to the `given` during its initialization:

```scala
final case class User(name: String, friends: List[User])

given OFormat[User] = OFormat.recursive {
(
(__ \ "name").format[String] and
(__ \ "friends").format[List[User]]
)(User.apply, user => (user.name, user.friends))
}
```

Equivalent methods are available on the `Reads`, `Writes`, `OWrites`, and
`Format` companion objects. The deferred codec can also be accessed within
the block using `summon[OFormat[User]]`. These methods defer codec
initialization; they do not make reading or writing deeply recursive values
stack-safe. Scala 2 code should continue to use the `lazyRead`, `lazyWrite`,
and `lazyFormat` methods.

## Format

[`Format[T]`](api/scala/play/api/libs/json/Format.html) is just a mix of the `Reads` and `Writes` traits and can be used for implicit conversion in place of its components.
Expand Down
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,48 @@
/*
* 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 =>

/**
* Constructs a `Format` for a recursive type.
*
* While `f` is evaluated, a deferred `Format[A]` is available as a
* contextual value. This lets formats derived inside `f` refer to the
* resulting format without forcing it during initialization.
*
* This method is available only in Scala 3.
*
* @tparam A the type read from and written as JSON
* @param f function that constructs the recursive format
*/
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,48 @@
/*
* 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 =>

/**
* Constructs an `OFormat` for a recursive type.
*
* While `f` is evaluated, a deferred `OFormat[A]` is available as a
* contextual value. This lets object formats derived inside `f` refer to
* the resulting format without forcing it during initialization.
*
* This method is available only in Scala 3.
*
* @tparam A the type read from and written as a JSON object
* @param f function that constructs the recursive object format
*/
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,45 @@
/*
* 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 =>

/**
* Constructs an `OWrites` for a recursive type.
*
* While `f` is evaluated, a deferred `OWrites[A]` is available as a
* contextual value. This lets object writers derived inside `f` refer to
* the resulting writer without forcing it during initialization.
*
* This method is available only in Scala 3.
*
* @tparam A the type written as a JSON object
* @param f function that constructs the recursive object writer
*/
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,45 @@
/*
* 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 =>

/**
* Constructs a `Reads` for a recursive type.
*
* While `f` is evaluated, a deferred `Reads[A]` is available as a
* contextual value. This lets readers derived inside `f` refer to the
* resulting reader without forcing it during initialization.
*
* This method is available only in Scala 3.
*
* @tparam A the type read from JSON
* @param f function that constructs the recursive reader
*/
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,45 @@
/*
* 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 =>

/**
* Constructs a `Writes` for a recursive type.
*
* While `f` is evaluated, a deferred `Writes[A]` is available as a
* contextual value. This lets writers derived inside `f` refer to the
* resulting writer without forcing it during initialization.
*
* This method is available only in Scala 3.
*
* @tparam A the type written as JSON
* @param f function that constructs the recursive writer
*/
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
Loading
Loading