Documentation

You are viewing the documentation for the 2.1.x release series. The latest stable release series is 3.0.x.

§JSON Reads[T]/Writes[T]/Format[T] Combinators

Please note this documentation was initially published as an article by Pascal Voitot (@mandubian) on mandubian.com

§Summary of main new features added in Play2.1

import play.api.libs.json._
import play.api.libs.functional.syntax._

val customReads: Reads[(String, Float, List[String])] = 
  (JsPath \ "key1").read[String](email keepAnd minLength(5)) and 
  (JsPath \ "key2").read[Float](min(45)) and
  (JsPath \ "key3").read[List[String]] 
  tupled
import play.api.libs.json.Json

val js = Json.obj(
  "key1" -> "alpha", 
  "key2" -> 123.345F, 
  "key3" -> Json.arr("alpha", "beta")
)

scala> customReads.reads(js) 
res5: JsSuccess(("alpha", 123.345F, List("alpha", "beta")))

customReads.reads(js).fold(
  invalid = { errors => ... },
  valid = { res => 
    val (s, f, l): (String, Float, List[String]) = res
    ...
  }
)
		

Now let’s go in the details ;)



§JsPath in a nutshell

You certainly know XMLPath in which you can access a node of an XML AST using a simple syntax based on path.
JSON is already an AST and we can apply the same kind of syntax to it and logically we called it JsPath.

All following examples use JSON defined in previous paragraph.

§Building JsPath

import play.api.libs.json._

// Simple path
JsPath \ "key1"

// 2-levels path
JsPath \ "key3" \ "key33"
 
// indexed path
(JsPath \ "key3" \ "key32")(2) // 2nd element in a JsArray

// multiple/recursive paths
JsPath \\ "key1"

§Alternative syntax

JsPath is quite cool but we found this syntax could be made even clearer to highlight Reads[T] combinators in the code.
That’s why we provide an alias for JsPath: __ (2 underscores).
You can use it or not. This is just a visual facility because with it, you immediately find your JsPath in the code…

You can write:

import play.api.libs.json._
import play.api.libs.functional.syntax._

// Simple path
__ \ "key1"

// 2-levels path
__ \ "key3" \ "key33"
 
// indexed path
(__ \ "key3" \ "key32")(2) // 2nd element in a JsArray

// multiple paths
__ \\ "key1"

// a sample on Reads[T] combinators to show the difference with both syntax
// DON'T TRY TO UNDERSTAND THIS CODE RIGHT NOW… It's explained in next paragraphs
val customReads = 
  (JsPath \ "key1").read(
    (JsPath \ "key11").read[String] and
    (JsPath \ "key11").read[String] and
    (JsPath \ "key11").read[String]
    tupled
  ) and 
  (JsPath \ "key2").read[Float](min(45)) and
  (JsPath \ "key3").read(
    (JsPath \ "key31").read[String] and
    (JsPath \ "key32").read[String] and
    (JsPath \ "key33").read[String]
    tupled
  ) 
  tupled
	
// with __
val customReads = 
  (__ \ "key1").read(
    (__ \ "key11").read[String] and
    (__ \ "key11").read[String] and
    (__ \ "key11").read[String]  
    tupled
  ) and 
  (__ \ "key2").read[Float](min(45)) and
  (__ \ "key3").read[List[String]] (
    (__ \ "key31").read[String] and
    (__ \ "key32").read[String] and
    (__ \ "key33").read[String]
    tupled  
  )
  tupled
	
// You can immediately see the structure of the JSON tree

§Accessing value of JsPath

The important function to retrieve the value at a given JsPath in a JsValue is the following:

sealed trait PathNode {
  def apply(json: JsValue): List[JsValue]
  …
}

As you can see, this function retrieves a List[JsValue]

You can simply use it like:

import play.api.libs.json._

// build a JsPath
scala> (__ \ "key1")(js) 
res12: List[play.api.libs.json.JsValue] = List("value1")  // actually this is JsString("value1")

// 2-levels path
scala> (__ \ "key3" \ "key33")(js)
res13: List[play.api.libs.json.JsValue] = List({"key":"value2","key34":"value34"})
 
// indexed path
scala> (__ \ "key3" \ "key32")(2)(js)
res14: List[play.api.libs.json.JsValue] = List(234.13)

// multiple paths
scala> (__ \\ "key1")(js)
res17: List[play.api.libs.json.JsValue] = List("value1", "value2")


§Reads[T] is now a validator

§Reads in Play2.0.x

Do you remember how you had to write a Json Reads[T] in Play2.0.x ?
You had to override the reads function.

trait Reads[A] {
  self =>
  /**
   * Convert the JsValue into a A
   */
  def reads(json: JsValue): A
}

Take the following simple case class that you want to map on JSON structure:

case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float
)

In Play2.0.x, you would write your reader as following:

import play.api.libs.json._

implicit val creatureReads = new Reads[Creature] {
  def reads(js: JsValue): Creature = {
    Creature(
      (js \ "name").as[String],
      (js \ "isDead").as[Boolean],
      (js \ "weight").as[Float]
    )
  }
}

scala> val js = Json.obj( "name" -> "gremlins", "isDead" -> false, "weight" -> 1.0F)
scala> val c = js.as[Creature] 
c: Creature("gremlins", false, 1.0F)

Easy isn’t it ?
So what’s the problem if it’s so easy?

Imagine, you pass the following JSON with a missing field:

val js = Json.obj( "name" -> "gremlins", "weight" -> 1.0F)

What happens?

java.lang.RuntimeException: Boolean expected
	at play.api.libs.json.DefaultReads$BooleanReads$.reads(Reads.scala:98)
	at play.api.libs.json.DefaultReads$BooleanReads$.reads(Reads.scala:95)
	at play.api.libs.json.JsValue$class.as(JsValue.scala:56)
	at play.api.libs.json.JsUndefined.as(JsValue.scala:70)

What is this?
Yes ugly RuntimeException (not even subtyped) but you can work around it using JsValue.asOpt[T] :)

scala> val c: Option[Creature] = js.asOpt[Creature]
c: None

Cool but you only know that the deserialization Json => Creature failed but not where or on which field(s)?



§Reads in Play2.1

We couldn’t keep this imperfect API as is and in Play2.1, the Reads[T] API has changed into :

trait Reads[A] {
  self =>
  /**
   * Convert the JsValue into a A
   */
  def reads(json: JsValue): JsResult[A]
}

Yes you have to refactor all your existing custom Reads but you’ll see you’ll get lots of new interesting features…

So you remark immediately JsResult[A] which is a very simple structure looking a bit like an Either applied to our specific problem.

JsResult[A] can be of 2 types:

case class JsSuccess[A](
  value: T, // the value retrieved when deserialization JsValue => A worked
  path: JsPath = JsPath() // the root JsPath where this A was read in the JsValue (by default, it's the root of the JsValue)
) extends JsResult[T]

// To create a JsSuccess from a value, simply do:
val success = JsSuccess(Creature("gremlins", false, 1.0))

Please note the greatest advantage of JsError is that it’s a cumulative error which can store several errors discovered in the Json at different JsPath

case class JsError(
  errors: Seq[(JsPath, Seq[ValidationError])]  
  // the errors is a sequence of JsPath locating the path 
  // where there was an error in the JsValue and validation 
  // errors for this path
) extends JsResult[Nothing]

// ValidationError is a simple message with arguments (which can be mapped on localized messages)
case class ValidationError(message: String, args: Any*)

// To create a JsError, there are a few helpers and for ex:
val errors1 = JsError( __ \ 'isDead, ValidationError("validate.error.missing", "isDead") )
val errors2 = JsError( __ \ 'name, ValidationError("validate.error.missing", "name") )

// Errors are cumulative which is really interesting
scala> val errors = errors1 ++ errors2
errors: JsError(List((/isDead,List(ValidationError(validate.error.missing,WrappedArray(isDead)))), (/name,List(ValidationError(validate.error.missing,WrappedArray(name))))))

So what’s interesting there is that JsResult[A] is a monadic structure and can be used with classic functions of such structures:

And some sugar such :

Please note that JsResult[A] is not just Monadic but Applicative because it cumulates errors.
This cumulative feature makes JsResult[T] makes it not very good to be used with for comprehension because you’ll get only the first error and not all.

§Reads[A] has become a validator

As you may understand, using the new Reads[A], you don’t only deserialize a JsValue into another structure but you really validate the JsValue and retrieve all the validation errors.
BTW, in JsValue, a new function called validate has appeared:

trait JsValue {
…
  def validate[T](implicit _reads: Reads[T]): JsResult[T] = _reads.reads(this)
  
  // same behavior but it throws a specific RuntimeException JsResultException now
  def as[T](implicit fjs: Reads[T]): T
  // exactly the same behavior has before
  def asOpt[T](implicit fjs: Reads[T]): Option[T]
…
}

// You can now write if you got the right implicit in your scope
val res: JsResult[Creature] = js.validate[Creature])

§Manipulating a JsResult[A]

So when manipulating a JsResult, you don’t access the value directly and it’s preferable to use map/flatmap/fold to modify the value.

import play.api.libs.json._

val res: JsResult[Creature] = js.validate[Creature]

// managing the success/error and potentially return something
res.fold(
  valid = { c => println( c ); c.name },
  invalid = { e => println( e ); e }
)

// getting the name directly (using the get can throw a NoSuchElementException if it's a JsError)
val name: JsSuccess[String] = res.map( creature => creature.name ).get

// filtering the result
val name: JsSuccess[String] = res.filter( creature => creature.name == "gremlins" ).get

// a classic Play action
def getNameOnly = Action(parse.json) { request =>
  val json = request.body
  json.validate[Creature].fold(
    valid = ( res => Ok(res.name) ),
    invalid = ( e => BadRequest(e.toString) )
  )
}

§Reads interesting new features

As you know, Json API for Play2.1 was still draft and has evolved since I began writing article part 1/2.
We have changed a few things since (nothing conceptual, just cosmetics).

§Reads[A <: JsValue] andThen Reads[B]

andThen has the classic Scala semantic of function composition : it applies Reads[A <: JsValue] on JSON retrieving a JsValue and then applies Reads[B] on this JsValue.

§Reads[A <: JsValue].map(f: A => B): Reads[B]

map is the classic and always very useful Scala map function.

§Reads[A <: JsValue].flatMap(f: A => Reads[B]): Reads[B]

flatMap is the classic Scala flatMap function.

§Rewriting the Reads[T] with JsResult[A]

The Reads[A] API returning a JsResult, you can’t write your Reads[A] as before as you must return a JsResult gathering all found errors.
You could imagine simply compose Reads[T] with flatMap :

Following code is WRONG

import play.api.libs.json._

// DO NOT USE, WRONG CODE
implicit val creatureReads = new Reads[Creature] {
  def reads(js: JsValue): JsResult[Creature] = {
    (js \ "name").validate[String].flatMap{ name => 
      (js \ "isDead").validate[Boolean].flatMap { isDead =>
	    (js \ "weight").validate[Float].map { weight =>
	      Creature(name, isDead, weight)
	    }
	  }
	}
  }
}

Remember the main purpose of JsResult is to gather all found errors while validating the JsValue.

JsResult.flatMap is pure monadic function (if you don’t know what it is, don’t care about it, you can understand without it) implying that the function that you pass to flatMap() is called only if the result is a JsSuccess else it just returns the JsError.
This means the previous code won’t aggregate all errors found during validation and will stop at first error which is exactly what we don’t want.

Actually, Monad pattern is not good in our case because we are not just composing Reads but we expect combining them following the schema:

Reads[String] AND Reads[Boolean] AND Reads[Float]   
  => Reads[(String, Boolean, Float)]   
     => Reads[Creature]

So we need something else to be able to combine our Reads and this is the greatest new feature that Play2.1 brings for JSON :
THE READS combinators with JsPath

If you want more theoretical aspects about the way it was implemented based on generic functional structures adapted to our needs, you can read this post “Applicatives are too restrictive, breaking Applicatives and introducing Functional Builders” written by @sadache

§Writing Reads[T] combinators

§Minimal import to work with combinators

// if you need Json structures in your scope
import play.api.libs.json._
// IMPORTANT import this to have the required tools in your scope
import play.api.libs.functional.syntax._

§Rewriting the Reads[T] with combinators

Go directly to the example as practice is often the best :

// IMPORTANT import this to have the required tools in your scope
import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val creatureReads = (
  (__ \ "name").read[String] and
  (__ \ "isDead").read[Boolean] and
  (__ \ "weight").read[Float]
)(Creature.apply _)  

// or in a simpler way as case class has a companion object with an apply function
implicit val creatureReads = (
  (__ \ "name").read[String] and
  (__ \ "isDead").read[Boolean] and
  (__ \ "weight").read[Float]
)(Creature)  

// or using the operators inspired by Scala parser combinators for those who know them
implicit val creatureReads = (
  (__ \ "name").read[String] ~
  (__ \ "isDead").read[Boolean] ~
  (__ \ "weight").read[Float]
)(Creature)  

So there is nothing quite complicated, isn’t it?

§(__ \ "name") is the JsPath where you gonna apply read[String]


§and is just an operator meaning Reads[A] and Reads[B] => Builder[Reads[A ~ B]]

§(…)(Creature) builds a Reads[Creature]

(__ \ "name").read[String] and (__ \ "isDead").read[Boolean] and (__ \ "weight").read[Float]

builds a

Builder[Reads[String ~ Boolean ~ Float])]

but you expect a Reads[Creature].

Try it:

scala> val js = Json.obj( "name" -> "gremlins", "isDead" -> false, "weight" -> 1.0F)
scala> js.validate[Creature] 
res1: play.api.libs.json.JsResult[Creature] = JsSuccess(Creature(gremlins,false,1.0),) 
// nothing after last comma because the JsPath is ROOT by default

Now what happens if you have an error now?

scala> val js = Json.obj( "name" -> "gremlins", "weight" -> 1.0F)
scala> js.validate[Creature] 
res2: play.api.libs.json.JsResult[Creature] = JsError(List((/isDead,List(ValidationError(validate.error.missing-path,WrappedArray())))))

Explicit, isn’t it?

§Complexifying the case

Ok, I see what you think : what about more complex cases where you have several constraints on a field and embedded Json in Json and recursive classes and whatever…

Let’s imagine our creature:

Now the class looks like:

case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float,
  email: String, // email format and minLength(5)
  favorites: (String, Int), // the stupid favorites
  friends: List[Creature] = Nil, // yes by default it has no friend
  social: Option[String] = None // by default, it's not social
)

Play2.1 provide lots of generic Reads helpers:

// import just Reads helpers in scope
import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.data.validation.ValidationError

// defines a custom reads to be reused
// a reads that verifies your value is not equal to a give value
def notEqualReads[T](v: T)(implicit r: Reads[T]): Reads[T] = Reads.filterNot(ValidationError("validate.error.unexpected.value", v))( _ == v )

def skipReads(implicit r: Reads[String]): Reads[String] = r.map( _.substring(2) )

implicit val creatureReads: Reads[Creature] = (
  (__ \ "name").read[String] and
  (__ \ "isDead").read[Boolean] and
  (__ \ "weight").read[Float] and
  (__ \ "email").read(email keepAnd minLength[String](5)) and
  (__ \ "favorites").read( 
  	(__ \ "string").read[String]( notEqualReads("ni") andKeep skipReads ) and
  	(__ \ "number").read[Int]( max(86) or min(875) )
  	tupled
  ) and
  (__ \ "friends").lazyRead( list[Creature](creatureReads) ) and
  (__ \ "social").readNullable[String]
)(Creature)  

Many things above can be understood very logically but let’s explain a bit:

§(__ \ "email").read(email keepAnd minLength[String](5))

§notEqualReads("ni") andKeep skipReads

§max(86) or min(875)

§(__ \ "favorites").read(…)

    (__ \ "string").read[String]( notEqualReads("ni") andKeep notEqualReads("swallow") ) and
    (__ \ "number").read[Int]( max(86) or min(875) )
    tupled

§(__ \ "friend").lazyRead( list[Creature](creatureReads) )

This is the most complicated line in this code. But you can understand why: the friend field is recursive on the Creature class itself so it requires a special treatment.

§(__ \ "social").readNullable[String]

Nothing quite complicated to understand: we need to read an option and readNullable helps in doing this.

Now we can use this Reads[Creature]

import play.api.libs.json._
import play.api.libs.functional.syntax._

val gizmojs = Json.obj( 
  "name" -> "gremlins", 
  "isDead" -> false, 
  "weight" -> 1.0F,
  "email" -> "[email protected]",
  "favorites" -> Json.obj("string" -> "alpha", "number" -> 85),
  "friends" -> Json.arr(),
  "social" -> "@gizmo"
)

scala> val gizmo = gizmojs.validate[Creature] 
gizmo: play.api.libs.json.JsResult[Creature] = JsSuccess(Creature(gremlins,false,1.0,[email protected],(pha,85),List(),Some(@gizmo)),)

val shaunjs = Json.obj( 
  "name" -> "zombie", 
  "isDead" -> true, 
  "weight" -> 100.0F,
  "email" -> "[email protected]",
  "favorites" -> Json.obj("string" -> "brain", "number" -> 2),
  "friends" -> Json.arr( gizmojs))

scala> val shaun = shaunjs.validate[Creature] 
shaun: play.api.libs.json.JsResult[Creature] = JsSuccess(Creature(zombie,true,100.0,[email protected],(ain,2),List(Creature(gremlins,false,1.0,[email protected],(alpha,85),List(),Some(@gizmo))),None),)

val errorjs = Json.obj( 
  "name" -> "gremlins", 
  "isDead" -> false, 
  "weight" -> 1.0F,
  "email" -> "rrhh",
  "favorites" -> Json.obj("string" -> "ni", "number" -> 500),
  "friends" -> Json.arr()
)

scala> errorjs.validate[Creature] 
res0: play.api.libs.json.JsResult[Creature] = JsError(List((/favorites/string,List(ValidationError(validate.error.unexpected.value,WrappedArray(ni)))), (/email,List(ValidationError(validate.error.email,WrappedArray()), ValidationError(validate.error.minlength,WrappedArray(5)))), (/favorites/number,List(ValidationError(validate.error.max,WrappedArray(86)), ValidationError(validate.error.min,WrappedArray(875))))))

§Reads[A] other features

§(Reads[A] and Reads[B]).tupled: Reads[(A, B)]

This useful to create Reads[TupleX]

(
  (__ \ 'field1).read[String] and 
  (__ \ 'field2).read[Int]
).tupled : Reads[(String, Int)]

It also works with JsArray and indexes

(
  (__(0)).read[String] and 
  (__(1)).read[Int]
).tupled : Reads[(String, Int)]

§(Reads[A1 <: A] and Reads[A2 <: A]).reduce(implicit reducer: Reducer[A, B]): Reads[B]

Useful to read several parts of a JSON and then aggregate them.
This one requires an implicit Reducer/Monoid.
We provide the ones for JsObject and JsArray.

Here are a few examples using Json transformers presented in next paragraph:

§Reduce a JsObject (copies branches and aggregates them in a JsObject)

(
  (__ \ 'field1).json.pickBranch[JsString] and 
  (__ \ 'field2).json.pickBranch[JsNumber]
).reduce : Reads[JsObject]

§Reduce a JsArray (copies leaf values and aggregates them in a JsArray)

(
  (__ \ 'field1).json.pick[JsString] and 
  (__ \ 'field2).json.pick[JsNumber]
).reduce : Reads[JsArray]


§Writes[T] hasn’t changed (except combinators)

§Writes in Play2.0.x

Do you remember how you had to write a Json Writes[T] in Play2.0.x ?
You had to override the writes function.

trait Writes[-A] {
  self =>
  /**
   * Convert the object into a JsValue
   */
  def writes(o: A): JsValue
}

Take the same simple case class we used in Part 1:

case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float
)

In Play2.0.x, you would write your Writes[Creature] as following (using new Json syntax to re-show it even if it didn’t exist in Play2.0.x ;) ):

import play.api.libs.json._

implicit val creatureWrites = new Writes[Creature] {
  def writes(c: Creature): JsValue = {
    Json.obj(
    	"name" -> c.name,
    	"isDead" -> c.isDead,
    	"weight" -> c.weight
    )
  }
}

scala> val gizmo = Creature("gremlins", false, 1.0F)
scala> val gizmojs = Json.toJson(gizmo)
gizmojs: play.api.libs.json.JsValue = {"name":"gremlins","isDead":false,"weight":1.0}

§Writes in Play2.1.x

No suspense to be kept: in Play2.1, you write Writes exactly in the same way :D

So what’s the difference?
As presented in Part 1, Reads could be combined using simple logical operators.
Using functional Scala power, we were able to provide combinators for Writes[T].

If you want more theoretical aspects about the way it was implemented based on generic functional structures adapted to our needs, you can read this post “Applicatives are too restrictive, breaking Applicatives and introducing Functional Builders” written by @sadache

§Writes main change: combinators

Once again, code first: re-writing previous Writes[T] using combinators.

// IMPORTANT import this to have the required tools in your scope
import play.api.libs.json._
// imports required functional generic structures
import play.api.libs.functional.syntax._

implicit val creatureWrites = (
  (__ \ "name").write[String] and
  (__ \ "isDead").write[Boolean] and
  (__ \ "weight").write[Float]
)(unlift(Creature.unapply))

// or using the operators inspired by Scala parser combinators for those who know them
implicit val creatureWrites = (
  (__ \ "name").write[String] ~
  (__ \ "isDead").write[Boolean] ~
  (__ \ "weight").write[Float]
)(unlift(Creature.unapply))

scala> val c = Creature("gremlins", false, 1.0F)
scala> val js = Json.toJson(c)
js: play.api.libs.json.JsValue = {"name":"gremlins","isDead":false,"weight":1.0}

It looks exactly like Reads[T] except a few things, isn’t it?
Let’s explain a bit (by copying Reads article changing just a few things… I’m lazy ;)):

§import play.api.libs.json.Writes._

It imports only the required stuff for Writes[T] without interfering with other imports.

§(__ \ "name").write[String]

You apply write[String] on this JsPath (exactly the same as Reads)

§and is just an operator meaning Writes[A] and Writes[B] => Builder[Writes[A ~ B]]

§(…)(unlift(Creature.unapply)) builds a Writes[Creature]

(__ \ "name").write[String] and (__ \ "isDead").write[Boolean] and (__ \ "weight").write[Float]` 

builds a

Builder[Writes[String ~ Boolean ~ Float])]` but you want a `Writes[Creature]

The only thing you have to keep in mind is this unlift call which might not be natural at first sight!

As you can deduce by yourself, the Writes[T] is far easier than the Reads[T] case because when writing, it doesn’t try to validate so there is no error management at all.

Moreover, due to this, you have to keep in mind that operators provided for Writes[T] are not as rich as for Reads[T]. Do you remind keepAnd and andKeep operators? They don’t have any meaning for Writes[T]. When writing A~B, you write A and B but not only A or only B. So and is the only operators provided for Writes[T].

§Complexifying the case

Let’s go back to our more complex sample used in end of Part1.
Remember that we had imagined that our creature was modelled as following:

case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float,
  email: String, // email format and minLength(5)
  favorites: (String, Int), // the stupid favorites
  friends: List[Creature] = Nil, // yes by default it has no friend
  social: Option[String] = None // by default, it's not social
)

Let’s write corresponding Writes[Creature]

// IMPORTANT import this to have the required tools in your scope
import play.api.libs.json._
// imports required functional generic structures
import play.api.libs.json.functional.syntax._

implicit val creatureWrites: Writes[Creature] = (
  (__ \ "name").write[String] and
  (__ \ "isDead").write[Boolean] and
  (__ \ "weight").write[Float] and
  (__ \ "email").write[String] and
  (__ \ "favorites").write( 
  	(__ \ "string").write[String] and
  	(__ \ "number").write[Int]
  	tupled
  ) and
  (__ \ "friends").lazyWrite(Writes.traversableWrites[Creature](creatureWrites)) and
  (__ \ "social").write[Option[String]]
)(unlift(Creature.unapply))  

val gizmo = Creature("gremlins", false, 1.0F, "[email protected]", ("alpha", 85), List(), Some("@gizmo"))
val gizmojs = Json.toJson(gizmo)
gizmojs: play.api.libs.json.JsValue = {"name":"gremlins","isDead":false,"weight":1.0,"email":"[email protected]","favorites":{"string":"alpha","number":85},"friends":[],"social":"@gizmo"}

val zombie = Creature("zombie", true, 100.0F, "[email protected]", ("ain", 2), List(gizmo), None)
val zombiejs = Json.toJson(zombie)
zombiejs: play.api.libs.json.JsValue = {"name":"zombie","isDead":true,"weight":100.0,"email":"[email protected]","favorites":{"string":"ain","number":2},"friends":[{"name":"gremlins","isDead":false,"weight":1.0,"email":"[email protected]","favorites":{"string":"alpha","number":85},"friends":[],"social":"@gizmo"}],"social":null

You can see that it’s quite straightforward. it’s far easier than Reads[T] as there are no special operator.
Here are the few things to explain:

§(__ \ "favorites").write(…)
  (__ \ "string").write[String] and
  (__ \ "number").write[Int]
  tupled

§(__ \ "friend").lazyWrite(Writes.traversableWrites[Creature](creatureWrites))

It’s the symmetric code for lazyRead to treat recursive field on Creature class itself:

FYI, you may wonder why Writes.traversableWrites[Creature]: Writes[Traversable[Creature]] can replace Writes[List[Creature]]?
This is because Writes[-T] is contravariant meaning: if you can write a Traversable[Creature], you can write a List[Creature] as List inherits Traversable (relation of inheritance is reverted by contravariance).

§Writes[A] other features

§Writes[A].contramap( B => A ): Writes[B]

Writes[A] is a contravariant functor that can be contramapped.
So you must give a function B => A to transform into a Writes[B].

For example:

scala> case class Person(name: String)
defined class Person

scala> __.write[String].contramap( (p: Person) => p.name )
res5: play.api.libs.json.OWrites[Person] = play.api.libs.json.OWrites$$anon$2@61df9fa8

§(Writes[A] and Writes[B]).tupled: Writes[(A, B)]

This useful to create Writes[TupleX]

(
  (__ \ 'field1).write[String] and 
  (__ \ 'field2).write[Int]
).tupled : Writes[(String, Int)]

Known limitation please note that the following doesn’t work: it compiles but it will break at runtime as Writes combinators only know how to generate JsObject but not JsArray.

// BE CAREFUL: IT COMPILES BUT BREAKS AT RUNTIME
(
  (__(0)).write[String] and 
  (__(1)).write[Int]
).tupled : Writes[(String, Int)]

§(Writes[A1 <: A] and Writes[A2 <: A]).join: Writes[A]

Useful to write the same value in several branches.

For example:

// Please note you must give the type of resulting Writes
scala> val jsWrites: Writes[JsString] = (
     |   (__ \ 'field1).write[JsString] and 
     |   (__ \ 'field2).write[JsString]
     | ).join
jsWrites: play.api.libs.json.Writes[play.api.libs.json.JsString] = play.api.libs.json.OWrites$$anon$2@732db69a

scala> jsWrites.writes(JsString("toto"))
res3: play.api.libs.json.JsObject = {"field1":"toto","field2":"toto"}

§What about combinators for Format?

Remember in Play2.1, there was a feature called Format[T] extends Reads[T] with Writes[T].
It mixed Reads[T] and Writes[T] together to provide serialization/deserialization at the same place.

Play2.1 provide combinators for Reads[T] and Writes[T]. What about combinators for Format[T] ?

Let’s go back to our very simple sample:

case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float
)

Here is how you write the Reads[Creature]:

import play.api.libs.json._
import play.api.libs.functional.syntax._

val creatureReads = (
  (__ \ "name").read[String] and
  (__ \ "isDead").read[Boolean] and
  (__ \ "weight").read[Float]
)(Creature)  

Please remark that I didn’t use implicit so that there is no implicit Reads[Creature] in the context when I’ll define Format[T]

Here is how you write the Writes[Creature]:

import play.api.libs.json._
import play.api.libs.functional.syntax._

val creatureWrites = (
  (__ \ "name").write[String] and
  (__ \ "isDead").write[Boolean] and
  (__ \ "weight").write[Float]
)(unlift(Creature.unapply))  

§How to gather both Reads/Writes to create a Format[Creature]?

§1st way = create from existing reads/writes

You can reuse existing Reads[T] and Writes[T] to create a Format[T] as following:

implicit val creatureFormat = Format(creatureReads, creatureWrites)

val gizmojs = Json.obj( 
  "name" -> "gremlins", 
  "isDead" -> false, 
  "weight" -> 1.0F
)

val gizmo = Creature("gremlins", false, 1.0F)

assert(Json.fromJson[Creature](gizmojs).get == gizmo)
assert(Json.toJson(gizmo) == gizmojs)

§2nd way = create using combinators

We have Reads and Writes combinators, isn’t it?
Play2.1 also provides Format Combinators due to the magic of functional programming (actually it’s not magic, it’s just pure functional programming;) )

As usual, code 1st:

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val creatureFormat = (
  (__ \ "name").format[String] and
  (__ \ "isDead").format[Boolean] and
  (__ \ "weight").format[Float]
)(Creature.apply, unlift(Creature.unapply))  

val gizmojs = Json.obj( 
  "name" -> "gremlins", 
  "isDead" -> false, 
  "weight" -> 1.0F
)

val gizmo = Creature("gremlins", false, 1.0F)

assert(Json.fromJson[Creature](gizmojs).get == gizmo)
assert(Json.toJson(gizmo) == gizmojs)

Nothing too strange…

§(__ \ "name").format[String]

It creates a format[String] reading/writing at the given JsPath

§( )(Creature.apply, unlift(Creature.unapply))

To map to a Scala structure:

So as Format[Creature] extends Reads[Creature] with Writes[Creature] we provide Creature.apply and unlift(Creature.unapply) and that’s all folks…

§More complex case

The previous sample is a bit dumb because the structure is really simple and because reading/writing is symmetric. We have:

Json.fromJson[Creature](Json.toJson(creature)) == creature

In this case, you read what you write and vis versa. So you can use the very simple JsPath.format[T] functions which build both Reads[T] and Writes[T] together.

But if we take our usual more complicated case class, how to write the Format[T]?

Remind the code:

import play.api.libs.json._
import play.api.libs.functional.syntax._

// The case class
case class Creature(
  name: String, 
  isDead: Boolean, 
  weight: Float,
  email: String, // email format and minLength(5)
  favorites: (String, Int), // the stupid favorites
  friends: List[Creature] = Nil, // yes by default it has no friend
  social: Option[String] = None // by default, it's not social
)

import play.api.data.validation.ValidationError
import play.api.libs.json.Reads._

// defines a custom reads to be reused
// a reads that verifies your value is not equal to a give value
def notEqualReads[T](v: T)(implicit r: Reads[T]): Reads[T] = Reads.filterNot(ValidationError("validate.error.unexpected.value", v))( _ == v )

def skipReads(implicit r: Reads[String]): Reads[String] = r.map( _.substring(2) )

val creatureReads: Reads[Creature] = (
  (__ \ "name").read[String] and
  (__ \ "isDead").read[Boolean] and
  (__ \ "weight").read[Float] and
  (__ \ "email").read(email keepAnd minLength[String](5)) and
  (__ \ "favorites").read( 
  	(__ \ "string").read[String]( notEqualReads("ni") andKeep skipReads ) and
  	(__ \ "number").read[Int]( max(86) or min(875) )
  	tupled
  ) and
  (__ \ "friends").lazyRead( list[Creature](creatureReads) ) and
  (__ \ "social").read(optional[String])
)(Creature)  

import play.api.libs.json.Writes._

val creatureWrites: Writes[Creature] = (
  (__ \ "name").write[String] and
  (__ \ "isDead").write[Boolean] and
  (__ \ "weight").write[Float] and
  (__ \ "email").write[String] and
  (__ \ "favorites").write( 
  	(__ \ "string").write[String] and
  	(__ \ "number").write[Int]
  	tupled
  ) and
  (__ \ "friends").lazyWrite(Writes.traversableWrites[Creature](creatureWrites)) and
  (__ \ "social").write[Option[String]]
)(unlift(Creature.unapply))

As you can see, creatureReads and creatureWrites are not exactly symmetric and couldn’t be merged in one single Format[Creature] as done previously.

Json.fromJson[Creature](Json.toJson(creature)) != creature

Hopefully, as done previously, we can build a Format[T] from a Reads[T] and a Writes[T].

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val creatureFormat: Format[Creature] = Format(creatureReads, creatureWrites)

// Testing Serialization of Creature to Json
val gizmo = Creature("gremlins", false, 1.0F, "[email protected]", ("alpha", 85), List(), Some("@gizmo"))
val zombie = Creature("zombie", true, 100.0F, "[email protected]", ("ain", 2), List(gizmo), None)

val zombiejs = Json.obj(
	"name" -> "zombie",
	"isDead" -> true,
	"weight" -> 100.0,
	"email" -> "[email protected]",
	"favorites" -> Json.obj(
		"string" -> "ain",
		"number" -> 2
	),
	"friends" -> Json.arr(
		Json.obj(
			"name" -> "gremlins",
			"isDead" -> false,
			"weight" -> 1.0,
			"email" -> "[email protected]",
			"favorites" -> Json.obj(
				"string" -> "alpha",
				"number" -> 85
			),
			"friends" -> Json.arr(),
			"social" -> "@gizmo"
		)
	),
	"social" -> JsNull
)

assert(Json.toJson(zombie) == zombiejs)

// Testing Deserialization of JSON to Creature (note the dissymetric reading)
val gizmo2 = Creature("gremlins", false, 1.0F, "[email protected]", ("pha", 85), List(), Some("@gizmo"))
val zombie2 = Creature("zombie", true, 100.0F, "[email protected]", ("n", 2), List(gizmo2), None)

assert(Json.fromJson[Creature](zombiejs).get == zombie2)

§Format[A] other features

§Format[A].inmap( A => B, B => A ): Format[B]

Format[A] is both covariant and contravariant (invariant) functor.
So you must give both functions A => B and B => A to transform into a Format[B].

For example:

scala> case class Person(name: String)
defined class Person

scala> __.format[String].inmap( (name: String) => Person(name), (p: Person) => p.name )
res6: play.api.libs.json.OFormat[Person] = play.api.libs.json.OFormat$$anon$1@2dc083c1

Next: JSON Transformers


Found an error in this documentation? The source code for this page can be found here. After reading the documentation guidelines, please feel free to contribute a pull request. Have questions or advice to share? Go to our community forums to start a conversation with the community.