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.

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…

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)?

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.

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:

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:

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

A ~ B just means Combine A and B but it doesn’t suppose the way it is combined (can be a tuple, an object, whatever…)

Builder is not a real type but I introduce it just to tell that the operator and doesn’t create directly a Reads[A ~ B] but an intermediate structure that is able to build a Reads[A ~ B] or to combine with another Reads[C]

JsPath.read[A](implicit reads:Reads[A]) can be passed a custom Reads[A] which is applied to the JSON content at this JsPath. So with this property, you can compose hierarchically Reads[T] which corresponds to JSON tree structure.

JsPath.readNullable allows Reads[Option[T]] with missing or empty field

Reads.email which validates the String has email format

Reads.minLength(nb) validates the minimum length of a String

Reads[A] or Reads[A] => Reads[A] operator is a classic OR logic operator

Reads[A] keepAnd Reads[B] => Reads[A] is an operator that tries Reads[A] and Reads[B] but only keeps the result of Reads[A] (For those who know Scala parser combinators keepAnd == <~ )

Reads[A] andKeep Reads[B] => Reads[B] is an operator that tries Reads[A] and Reads[B] but only keeps the result of Reads[B] (For those who know Scala parser combinators andKeep == ~> )

has min length of 5 (we precise the type here because minLength is a generic Reads[T])

Why not email and minLength[String](5)? because, as explained previously, it would generate a Builder[Reads[(String, String)]] whereas you expect a Reads[String]. The keepAnd operator (aka <~) does what we expect: it validates both sides but if succeeded, it keeps only the result on left side.

No need to write notEqualReads[String]("ni") because String type is inferred from (__ \ "knight").read[String] (the power of Scala typing engine)

skipReads is a customReads that skips the first 2 chars

andKeep operator (aka ~>) is simple to undestand : it validates the left and right side and if both succeeds, only keeps the result on right side. In our case, only the result of skipReads is kept and not the result of notEqualReads.

What means tupled ?Builder[Reads[(String, Int)]] can be used with a case class apply function to build the Reads[Creature] for ex. But it provides also tupled which is quite easy to understand : it “tuplizes” your Builder: Builder[Reads[(A, B)]].tupled => Reads[(A, B)]

Finally (__ \ "favorites").read(Reads[(String, Int)] will validate a (String, Int) which is the expected type for field favorites

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.

list[Creature](…) creates a Reads[List[Creature]]

list[Creature](creatureReads) passes explicitly creatureReads as an argument because it’s recursive and Scala requires that to resolve it. Nothing too complicated…

(__ \ "friend").lazyRead[A](r: => Reads[A])) : lazyRead expects a Reads[A] value passed by name to allow the type recursive construction. This is the only refinement that you must keep in mind in this very special recursive case.

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

A ~ B just means Combine A and B but it doesn’t suppose the way it is combined (can be a tuple, an object, whatever…)

Builder is not a real type but I introduce it just to tell that the operator and doesn’t create directly a Writes[A ~ B] but an intermediate structure that is able to build a Writes[A ~ B] or to combine with another Writes[C]

builds a Builder[Writes[String ~ Boolean ~ Float])] whereas you want a Writes[Creature]. - So you apply the Builder[Writes[String ~ Boolean ~ String])] to a function Creature => (String, Boolean, Float) to finally obtain a Writes[Creature].Please note that it may seem a bit strange to provide Creature => (String, Boolean, Float) to obtain a Writes[Creature] from a Builder[Writes[String ~ Boolean ~ String])] but it’s due to the contravariant nature of Writes[-T]. - We have Creature.unapply but its signature is Creature => Option[(String, Boolean, Float)] so we unlift it to obtain Creature => (String, Boolean, Float).

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].

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

Writes.traversableWrites[Creature](creatureWrites) creates a Writes[Traversable[Creature]] passing the Writes[Creature] itself for recursion (please note that a list[Creature]should appear very soon ;))

(__ \ "friends").lazyWrite[A](r: => Writes[A])) : lazyWrite expects a Writes[A] value passed by name to allow the type recursive construction. This is the only refinement that you must keep in mind in this very special recursive case.

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).