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
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ lazy val docs = project
val base = baseDirectory.value / "manual" / "working" / "scalaGuide"
val code = (base ** "code").get()

if (isScala3.value) code
if (isScala3.value) code ++ (base ** "code-3").get()
else code ++ (base ** "code-2").get()
},
PlayDocsKeys.resources += Def.uncached {
Expand Down
11 changes: 10 additions & 1 deletion docs/manual/working/scalaGuide/main/json/ScalaJsonCombinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,16 @@ There are a few differences between complex `Writes` and `Reads`:

- The individual path `Writes` are created using the `JsPath.write` method.
- There is no validation on conversion to `JsValue` which makes the structure simpler and you won't need any validation helpers.
- The intermediary `FunctionalBuilder#CanBuildX` (created by `and` combinators) takes a function that translates a complex type `T` to a tuple matching the individual path `Writes`. Although this is symmetrical to the `Reads` case, the `unapply` method of a case class returns an `Option` of a tuple of properties and must be used with `unlift` to extract the tuple.

The intermediary `FunctionalBuilder#CanBuildX` (created by `and` combinators) takes a function that translates a complex type `T` to a tuple matching the individual path `Writes`.

In Scala 2, although this is symmetrical to the `Reads` case, the `unapply` method of a case class returns an `Option` of a tuple of properties and must be used with `unlift` to extract the tuple.

@[scala2-writes-model](code-2/Scala2JsonCombinatorsSpec.scala)

In Scala 3, the `unapply` method of a case class is option-less by default, returning a tuple of its properties directly. It can therefore be used without `unlift`, using `Tuple.fromProductTyped` to extract the tuple.

@[scala3-writes-model](code-3/Scala3JsonCombinatorsSpec.scala)

### Functional combinators with Writes

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

package scalaguide.json

import org.specs2.mutable.Specification

final class Scala2JsonCombinatorsSpec extends Specification {
"Scala 2 JSON" should {
"allow creating Writes for model (using unlifted unapply)" in {
//#scala2-writes-model
import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val residentWrites: Writes[Resident] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))

implicit val placeWrites: Writes[Place] = (
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location] and
(JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))

val place = Place(
"Watership Down",
Location(51.235685, -1.309197),
Seq(
Resident("Fiver", 4, None),
Resident("Bigwig", 6, Some("Owsla"))
)
)

val json = Json.toJson(place)
//#scala2-writes-model

(json \ "name").get must_=== JsString("Watership Down")
}

}

// ---

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package scalaguide.json

import org.specs2.mutable.Specification

final class Scala2JsonCombinatorsSpec extends Specification {
"Scala 2 JSON" should {
"allow creating Writes for model (using unlifted unapply)" in {
//#scala3-writes-model
import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(Tuple.fromProductTyped(_: Location))

implicit val residentWrites: Writes[Resident] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "role").writeNullable[String]
)(Tuple.fromProductTyped(_: Resident))

implicit val placeWrites: Writes[Place] = (
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location] and
(JsPath \ "residents").write[Seq[Resident]]
)(Tuple.fromProductTyped(_: Place))

val place = Place(
"Watership Down",
Location(51.235685, -1.309197),
Seq(
Resident("Fiver", 4, None),
Resident("Bigwig", 6, Some("Owsla"))
)
)

val json = Json.toJson(place)
//#scala3-writes-model

(json \ "name").get must_=== JsString("Watership Down")
}

}

// ---

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
}