Search code examples
jsonscalaplayframework-2.0combinators

What is this "and" in ScalaJsonCombinator (when defining a Writes)?


I've been using this json combinator for several basic / standard cases without really understanding how it works. All was fine.

Now I want to get myself prepared for whatever advanced cases might come; I need to understand the code.

Ref.: https://www.playframework.com/documentation/2.3.x/ScalaJsonCombinators

I think I can understand the Reads:

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]
)(Location.apply _)

It creates a Reads that:

  1. First -- when given a JsValue (through its "reads" method) -- it pulls the "lat", followed by the "long". Out of those two it creates a tuple (Double, Double). -- https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.libs.json.Reads

  2. That tuple is then assigned to the partial function of that Reads..., which in this case is whatever returned by "Location.apply _". I tried it in repl:

...

scala> val yowMan = Location.apply _
yowMan: (Double, Double) => Location = <function2>

scala> yowMan(1, 2)
res14: Location = Location(1.0,2.0)

That partial function takes the a tuple of (Double, Double) as input. So..., the outcome of step 1 is channeled to step 2, and we get an instance of Location as the return of "reads".

Now for the Writes:

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

First the "unapply". I tried in repl:

scala> val heyDude = Location.unapply
<console>:16: error: missing arguments for method unapply in object Location;
follow this method with `_' if you want to treat it as a partially applied function
   val heyDude = Location.unapply

Oops, ok, I followed the instruction:

scala> val heyDude = Location.unapply _
heyDude: Location => Option[(Double, Double)] = <function1>

Ok, so we get a partial function that transforms an instance of Location to an (optional) tuple of (Double, Double).

Next, the "unlift":

scala> val hohoho = unlift(heyDude)
hohoho: Location => (Double, Double) = <function1>

scala> val loc = Location(1, 2)
loc: Location = Location(1.0,2.0)

scala> hohoho(loc)
res16: (Double, Double) = (1.0,2.0)

Ok, so... unlift simply throws away the "Option", and takes us directly to the tuple.

Ok... so... I guess... this "writes" of the Writes... *) when given an instance of Location, it will:

  1. Pass that object through that partial function produced by unlift(Location.unapply).

  2. The tuple (Double, Double) returned by that partial function is then channeled to whatever is produced by this:

    (JsPath \ "lat").write[Double] and (JsPath \ "long").write[Double]

What exactly is that "whatever"? Following the API doc of JsPath, I think it is OWrites: https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.libs.json.OWrites

But... I can't see there's a method named "and" in OWrites. Where is this "and" declared? And what does it do? Is it: "oWrites1 and oWrites2" produces "oWrites3"? And this "oWrites3" is a special type of OWrites that takes tuple as input? ... If that's the case... the tuple doesn't have information about the name of the property in the case class ("lat" and "long"). How does it know that the produced json string should be {"lat": 1, "long": 2} then?

Sorry for the train of questions. Please help me obtaining a clear understanding of this. Thanks!

*) https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.libs.json.Writes


UPDATES:


Solution

  • When in doubt, decompile it. This showed that there is an implicit toFunctionalBuilderOps, which then you can see in FunctionalBuilderOps that there is your and method