Play JSON Reads/Writes with single-parameter case classes - json

This creates a Writes for a case class
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class A(a: String, b: String, c: String)
(JsPath.write[String] and
JsPath.write[String] and
JsPath.write[String])(unlift(A.unapply))
This can be extended to work for 2, 3, 4, 5, 6, etc. parameters...but not 1.
case class B(a: String)
(JsPath.write[String])(unlift(B.unapply))
Compiler error:
error: overloaded method value write with alternatives:
(t: String)(implicit w: play.api.libs.json.Writes[String])play.api.libs.json.OWrites[play.api.libs.json.JsValue] <and>
(implicit w: play.api.libs.json.Writes[String])play.api.libs.json.OWrites[String]
cannot be applied to (B => String)
(JsPath.write[String])(unlift(B.unapply))
^
A similar problem happens for Reads.
How can I get Reads and Writes for single-parameter case clases?

Like Travis said:
Transforming an existing Reads: use the map method
Transforming an existing Writes: use contramap
However, contramap only works on Writes that produce JsObject. Your writes will fail at runtime:
val w = JsPath.write[String].contramap[B](_.a)
scala> w.writes(B("Hello"))
java.lang.RuntimeException: when empty JsPath, expecting JsObject
You can create a Writes "from scratch" using Writes.apply:
Writes[B](b => JsString(b.a))
Similarly you can create a Reads using Reads.apply.

implicit val reads: Reads[A] =
(JsPath \ "ax").read[B].map(A.apply)

Related

Get concrete type of a case class

I have this:
sealed trait Block
sealed case class Header(param1: String,
param2: String,
...) extends Block
...
(more sealed case classes that follows the same pattern)
Later, i'm grouping this blocks in a Seq, as follows:
val blocks: Seq[Block with Product with Serializable] = Seq(structure.header, structure.body, ...)
And i want to serialize every block as Json (with Play). I'm doing it as follows:
blocks.map{
x =>
serializeBlock(x)
}
Definition of "serializeBlock":
def serializeBlock[A<:Block](block:A): String = {
block match {
case block: Header => Json.toJson(block).toString()
case block: Body => Json.toJson(block).toString()
... n-times for every possible case class that mixes the block trait
}
}
I have readers and writers for every concrete block (header, body...) but, as you see, when i mix these blocks in a Seq Scala treats it as generic type Block, for this reason i'm doing the pattern matching with every possible block type (implicit casting?). If i simply call "Json.toJson" in the Map Play complains about not finding a Reader/Writer for "block" type.
A "Block" is a fragment of a relatively big JSON. I'm getting the JSON, i'm splitting it in qualified "blocks" and then i'm saving it as a String in a Database:
"Big" JSON:
{
"header" : {
"param1" : "",
...
},
"body" : {
"param1" : "",
...
}
...
}
Blocks
{
"param1" : "",
...
}
My question is: Is there any way to do the serialization without repeating n-times the "block: type" pattern? I mean: is there any way to get the concrete type of that block (knowing that the Seq is typed as superclass "block" and not as the "concrete" type of that block)?
EDIT
I have a Reader/Writer for every block as follows:
implicit val headerReader: Reads[Header] = (
(JsPath \ "param1").read[String] and
(JsPath \ "param2").read[String] and
...
)(Header.apply _)
implicit val headerWriter: Writes[Header] = (
(JsPath \ "param1").write[String] and
(JsPath \ "param2").write[String] and
...
)(unlift(Header.unapply))
EDIT 2:
Is Shapeless a way to solve this?
EDIT 3:
As Andrzej Jozwik noted: "param1" and "param2" are 'wildcard' params that i've used to define my JSON Structure here. Every block has different params.
shapeless' HList seems like a possible solution for you. Here's an example that seems pretty close to what you want to do:
import shapeless._
sealed trait Block
case class Test1(a: String, b: String) extends Block
object Test1 {
implicit val writes = (
(JsPath \ "a").write[String] and
(JsPath \ "b").write[String]
)(unlift(Test1.unapply))
}
case class Test2(c: String, d: String) extends Block
object Test2 {
implicit val writes =(
(JsPath \ "c").write[String] and
(JsPath \ "d").write[String]
)(unlift(Test2.unapply))
}
object serializeBlock extends Poly1 {
implicit def default[T <: Block](implicit w: Writes[T]) = at[T] { x =>
Json.toJson(x).toString
}
}
val blocks = Test1("hi", "hello") :: Test2("what", "why") :: HNil
blocks.map(serializeBlock).toList // List[String]({"a": "hi", "b": "hello"}, {"c": "what", "d": "why"})
Note that each member of the HList must have an implicit Writes available for that type. If it doesn't the error you get isn't wildly helpful:
val list = Test1("hi", "hello") :: Test2("a", "b") :: NoWrites("wrong") :: HNil
list.map(serializeBlock)
with the error:
could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[serializeBlock.type,shapeless.::[Test1,shapeless.::[Test2,shapeless.::[NoWrites,shapeless.HNil]]]]
This just means that it is not possible to call serializeBlock for some member of the HList you called map on.
In this case, it can't call serializeBlock on the NoWrites object because there is no implicit Writes[NoWrites] available in the current scope.
You will get a similar error if any object in the HList does not extend Block.

Play Scala Json Writer for Seq of Tuple

I'm trying to find a way to use the built in Macro Json Writer in order to serialize Seq[(String,Customer)]
I managed to do this for Seq[Customer] but when adding the touple, the compiler starts screaming at me.
This code works:
package models.health
import play.api.libs.json._
case class Customer(name: String, age: Int)
//we use the dummy var as a workaround to the json writer limitations (cannot handle single argument case class)
case class Demo(customers: Seq[Customer], dummy: Option[String] = None)
object Demo {
import play.api.libs.functional.syntax._
implicit val customer_writer = Json.writes[Customer]
implicit val writes: Writes[Demo] = (
(__ \ "customers").write[Seq[Customer]] and
(__ \ "dummy").writeNullable[String]) {
(d: Demo) => (d.customers,d.dummy)
}
}
BUT the below code (simply change from Seq[Customer] to Seq[(String,Customer)] Doesn't Copmile... Any help is really appreciated:
package models.health
import play.api.libs.json._
case class Customer(name: String, age: Int)
//we use the dummy var as a workaround to the json writer limitations (cannot handle single argument case class)
case class Demo(customers: Seq[(String,Customer], dummy: Option[String] = None)
object Demo {
import play.api.libs.functional.syntax._
implicit val customer_writer = Json.writes[Customer]
implicit val writes: Writes[Demo] = (
(__ \ "customers").write[Seq[(String,Customer)]] and
(__ \ "dummy").writeNullable[String]) {
(d: Demo) => (d.customers,d.dummy)
}
}
this is the compiler error I got:
No Json serializer found for type Seq[(String,models.health.Customer)]
The library makes no assumption as to how you want your tuple to serialize. You could use an array, an object, etc.
By adding this implicit Writes function, your serializer will write it out as an array.
implicit def tuple2Writes[A, B](implicit a: Writes[A], b: Writes[B]): Writes[Tuple2[A, B]] = new Writes[Tuple2[A, B]] {
def writes(tuple: Tuple2[A, B]) = JsArray(Seq(a.writes(tuple._1), b.writes(tuple._2)))
}

Defining Writes for case class that contains property whose type is a Map . Json / Scala / PlayFramework

I have a case class defined this way:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> import play.api.libs.functional.syntax._
import play.api.libs.functional.syntax._
scala> type MapIntString = Map[Int, String]
defined type alias MapIntString
case class Duh(a: MapIntString)
defined class Duh
When I tried declaring its Writes this way, I got an error:
scala> implicit val duhWrites = Json.writes[Duh]
<console>:25: error: No implicit Writes for MapIntString available.
implicit val duhWrites = Json.writes[Duh]
So I resort to this..., still got an error:
scala> implicit val duhWrites: Writes[Duh] = (
| (JsPath \ "a").write[MapIntString]
| )(unlift(Duh.unapply _))
<console>:26: error: overloaded method value write with alternatives:
(t: MapIntString)(implicit w: play.api.libs.json.Writes[MapIntString])play.api.libs.json.OWrites[play.api.libs.json.JsValue] <and>
(implicit w: play.api.libs.json.Writes[MapIntString])play.api.libs.json.OWrites[MapIntString]
cannot be applied to (Duh => MapIntString)
(JsPath \ "a").write[MapIntString]
Can somebody help me pointing out where I did wrong?
I feel that my understanding of this reads/writes combinators a bit shaky. I posted related question here (when I was trying to understand what was going on): What is this "and" in ScalaJsonCombinator (when defining a Writes)?
If you know the best way to implement the Writes and Reads for "Duh" please share.
Thanks in advance!
There are no predefined Writes for Map[Int, String], only Map[String, A]. This is because all JSON keys must be a String, to Int keys don't really make any sense. You can fix this by defining one that converts the Int keys to Strings.
type MapIntString = Map[Int, String]
implicit val mapIntWrites = new Writes[MapIntString] {
def writes(m: MapIntString): JsValue =
Json.toJson(m.map { case (k, v) => (k.toString, v)} )
}
scala> implicit val duhWrites = Json.writes[Duh]
duhWrites: play.api.libs.json.OWrites[Duh] = play.api.libs.json.OWrites$$anon$2#50973f47

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:
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
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:
Pass that object through that partial function produced by unlift(Location.unapply).
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:
Adding related question: Syntax and meaning of a Scala/Play! code sample
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

How to write a Reads[Seq[T]] for a given Reads[T]?

I'm using the json library of the play framework version 2.2.3. I have the following json object:
{
"myData":
[
{
"A": "some text",
"B": [10, 20, 30]
},
{
"A": "some other text",
"B": [15, 25, 35]
},
...
]
}
I want to deserialize this json object to a Vector[Map[String, Vector[Int]]]. So the result should be:
Vector(Map("some text" -> Vector(10, 20, 30)), Map("some other text" -> Vector(15, 25, 35)))
While I was trying to achieve that, I was able to write a Reads[Map[String, Vector[Int]]] that does it for a single entry.
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val singleEntryReads: Reads[Map[String, Vector[Int]]] = {
(__).read(
(__ \ "A").read[String] and
(__ \ "B").read[Vector[Int]] tupled) map { keyAndValue =>
val (a, b) = keyAndValue
Map(a -> b)
}
}
So the conversion works for a single entry:
scala> (myJsonObject \ "myData")(0).validate[Map[String, Vector[Int]]]
res: play.api.libs.json.JsResult[Map[String, Vector[Int]]] = JsSuccess(Map(some text -> Vector(10, 20, 30)))
But how can I write a Reads[Vector[Map[String, Vector[Int]]]]? My best idea was to do it similar to the answer given to this older question:
implicit val allEntriesReads: Reads[Seq[Map[String, Vector[Int]]]] = Reads.seq(singleEntryReads)
I tried to use it like this:
scala> (myJsonObject \ "myData").validate[Seq[Map[String, Vector[Int]]]]"
res2: play.api.libs.json.JsResult[Seq[Map[String, Vector[Int]]]] = JsError(List(((147)/B,List(ValidationError(error.path.missing,WrappedArray()))), ((148)/B,List(ValidationError(error.path.missing,WrappedArray())))))
But that does not work and gives me a JsError(). How do I have to implement the second Reads to get it working that way?
You don't need to. Play! comes with an implicit (defined in the Reads companion object) that already does what you want:
implicit def traversableReads[F[_], A](implicit bf: CanBuildFrom[F[_], A, F[A]], ra: Reads[A]): Reads[F[A]]
If you have an implicit Reads[A] and an appropriate CanBuildFrom for the sequence type (these already exist for the standard libraries collection types, e.g. Vector) then this implicit will act as an implicit Reads[F[A]] where F is the collection type.
Scala lets you define an implicit def that itself takes implicit parameters, and it will act as an implicit value of its return type. When using your implicit def, Scala will search for the implicit parameters at the call site. So:
.validate[Seq[Map[String, Vector[Int]]]]
becomes:
.validate[Seq[Map[String, Vector[Int]]]](traversableReads)
which then becomes:
.validate[Seq[Map[String, Vector[Int]]]](traversableReads(singleEntryReads, Seq.canBuildFrom)