Play Scala Json Writer for Seq of Tuple - json

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

Related

Error message: Try to implement an implicit Writes or Format for this type

I have the following json string. In scala I want to extract some fields and save them in a new json.
{"query":"doi:10.1186/s13612-016-0045-3","result":[{"total":"1","start":"1","pageLength":"10"}],"records":[{"identifier":"doi:10.1186/s13612-016-0045-3","url":[{"format":"","platform":"","value":"http://dx.doi.org/10.1186/s13612-016-0045-3"}],"title":"Technology
and Reflection: Mood and Memory Mechanisms for
Well-Being","creators":[{"creator":"Konrad,
Artie"},{"creator":"Tucker, Simon"},{"creator":"Crane,
John"},{"creator":"Whittaker, Steve"}],"publicationName":"Psychology
of
Well-Being","issn":"2211-1522","openaccess":"true","journalid":"13612","doi":"10.1186/s13612-016-0045-3","publisher":"Springer","publicationDate":"2016-06-15","volume":"6","number":"1","issuetype":"","topicalCollection":"","startingPage":"1","copyright":"©2016
The
Author(s)","genre":"OriginalPaper","abstract":"AbstractBackgroundWe
report a
..."}],"facets":[{"name":"subject","values":[{"value":"Biological
Psychology","count":"1"},{"value":"Health
Psychology","count":"1"},{"value":"Neuropsychology","count":"1"},{"value":"Positive
Psychology","count":"1"},{"value":"Psychology","count":"1"},{"value":"Quality
of Life
Research","count":"1"}]},{"name":"pub","values":[{"value":"Psychology
of
Well-Being","count":"1"}]},{"name":"year","values":[{"value":"2016","count":"1"}]},{"name":"country","values":[{"value":"United
States","count":"1"},{"value":"USA","count":"1"}]},{"name":"type","values":[{"value":"Journal","count":"1"}]},{"name":"keyword","values":[{"value":"Emotion","count":"1"},{"value":"Memory","count":"1"},{"value":"Mood","count":"1"},{"value":"Reflection","count":"1"},{"value":"Reminiscence","count":"1"},{"value":"Technology
mediated
reflection","count":"1"},{"value":"Well-being","count":"1"}]}]}
In particular I want to get the following new json object (i.e. extract fields "creators","publicationName" and "abstract", and rename them accordingly):
{"creators":[{"creator":"Konrad, Artie"},{"creator":"Tucker,
Simon"},{"creator":"Crane, John"},{"creator":"Whittaker,
Steve"}],"pubTitle":"Psychology of
Well-Being","pubAbstract":"AbstractBackgroundWe report ..."}
This is my current code, but I the error message says Error:(137, 27) No Json serializer found for type play.api.libs.json.JsResult[org.test.Publication]. Try to implement an implicit Writes or Format for this type. val json = Json.toJson(processedPubl).
import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.data.validation.ValidationError
import play.api.libs.json.Reads._
case class Creator(creator: String)
case class Publication(pubTitle: String, creators: Seq[Creator], pubAbstract: String)
val jsonstring = ... // this is the raw string provided
// at the beginning of this post
implicit val publReads: Reads[Publication] = (
(JsPath \ "publicationName").read[String] and
(JsPath \ "creators").read[Seq[Creator]] and
(JsPath \ "abstract").read[String]
) (Publication.apply _)
val processedPubl = Json.parse(jsonstring).validate[Publication](publReads)
val json = Json.toJson(processedPubl)
You should try this:
import play.api.libs.functional.syntax._
import play.api.libs.json._
case class Creator(creator: String)
case class Publication(pubTitle: String, creators: Seq[Creator], pubAbstract: String)
val jsonString = ...
implicit val creatorFormat = Json.format[Creator]
implicit val publWrites = Json.writes[Publication]
implicit val publReads: Reads[Publication] = (
(JsPath \ "publicationName").read[String] and
(JsPath \ "creators").read[Seq[Creator]] and
(JsPath \ "abstract").read[String]
)(Publication.apply _)
val input = Json.parse(jsonString).as[Publication]
val output = Json.toJson(input)
Reads is something that is used during reading from JSON (input uses this) and Writes is something that is used during writing to JSON (output uses it). Together they form something called Format. Since you're using case classes in your code, you can autogenerate those Reads/Writes/Formats using play-json methods.

Playframework Akka WebSocket.accept How to use derived classes and MessageFlowTransformer

Using Playframework 2.5, Akka and Websockets I would like to be able to declare a websocket that accepts different incoming and outcoming classes, those classes would have a base class (one base class for incoming and another one for outcoming messages). Here's where I am :
object IncomingMessage {
val subscribeMessage = "subscribe"
val unsubscribeMessage = "unsubscribe"
}
abstract class IncomingMessage(action: String)
case class SubscribeMessage(repository: String, interval: Int, action: String = IncomingMessage.subscribeMessage) extends IncomingMessage(action)
case class UnsubscribeMessage(repository: String, action: String = IncomingMessage.unsubscribeMessage) extends IncomingMessage(action)
object SubscribeMessage {
implicit val SubscribeMessageWrites: Reads[SubscribeMessage] = (
(JsPath \ "repository").read[String] and
(JsPath \ "interval").read[Int](min(1)) and
(JsPath \ "action").read[String]
)(SubscribeMessage.apply _)
}
object UnsubscribeMessage {
implicit val UnsubscribeMessageWrites: Reads[UnsubscribeMessage] = (
(JsPath \ "repository").read[String] and
(JsPath \ "action").read[String]
)(UnsubscribeMessage.apply _)
}
The outcoming messages are based on the same principal so I will not show them.
Reading the Play documentation I would implement something like this
import play.api.libs.json._
import play.api.mvc.WebSocket.FrameFormatter
implicit val incomingFormat = Json.format[IncomingMessage]
implicit val outcomingFormat = Json.format[OutcomingMessage]
implicit val messageFlowTransformer = MessageFlowTransformer.jsonMessageFlowTransformer[IncomingMessage, OutcomingMessage]
...
def socket = WebSocket.acceptOrResult[IncomingMessage, OutcomingMessage] { ... }
I would like to use formatters and the MessageFlowTransformer to be able to send messages without transforming them to json explicitly, so I don't want to write :
out ! Json.toJson(outcomingMessage)
But I would like to write
out ! outcomingMessage
But I get back an error during compile time for the implicit val incomingFormat AND the implicit val outcomingFormat
No unapply or unapplySeq function found
How can I solve this issue ?
Is it possible to use derived classes as websocket messages using actors ?

Is it possible to override the way play.api.libs.json write options?

I work with Play! scala 2.4 and I have several simple case class like this one:
case class A(a: Option[String])
I would like to override the way Json.toJson(A) works when the option is empty.
Here is what I have done:
implicit val aWrites: Writes[A] = Json.writes[A]
implicit def ow[T](implicit w: Writes[T]): Writes[Option[T]] = Writes {
case None => JsString("[]")
case Some(t) => Json.toJson(t)
}
Json.toJson(A(a = None)) mustBe Json.parse("""{"a":"[]"}""")
but it still parses the class as usual (i.e. {} instead of {"a":"[]"} as I would like).
What can I do in order to make this test pass?
This isn't possible using the Json.writes macro, because it is written to specially handle Option to use readNullable[B]. This means you will need to need to use combinators to define Writes[A]. Unfortunately, defining Writes for an object with only one field is a little more cumbersome than one that has more.
implicit val aWrites: Writes[A] = Writes(a => Json.obj("a" -> a.a))
scala> Json.toJson(A(None))
res8: play.api.libs.json.JsValue = {"a":"[]"}
Example with multiple fields:
import play.api.libs.functional.syntax._
import play.api.libs.json._
case class A(a: Option[String], b: Int)
implicit val aWrites: Writes[A] = (
(__ \ "a").write[Option[String]] and
(__ \ "b").write[Int]
)(unlift(A.unapply))
scala> Json.toJson(A(None, 10))
res0: play.api.libs.json.JsValue = {"a":"[]","b":10}
You need to change your aWrites with the one below to add "a": part:
implicit val aWrites: Writes[A] = new Writes[A] {
override def writes(o: A): JsValue =
Json.obj("a" -> Json.toJson(o.a))
}
EDIT: Using apply method of Writes object is clearer and a better practice for Scala, so I suggest you to use #m-z's answer.

Scala / Play 2.4 JSON Format issue

I have following class (simplified a bit there) that would extend the JSON-format for certain objects that represent the Database-level with the ID-field:
import play.api.libs.json._
import play.api.libs.functional.syntax._
class EntityFormat[T <: Entity](entityFormatter: Format[T]) extends Format[T] {
val extendedFormat: Format[T] = (
__.format[T](entityFormatter) ~
(__ \ "id").format[Option[Long]]
)(tupleToEntity, entityToTuple)
private def tupleToEntity(e: T, id: Option[Long]) = {
e.id = id
e
}
private def entityToTuple(e: T) = (e, e.id)
def writes(o: T): JsValue = extendedFormat.writes(o)
def reads(json: JsValue): JsResult[T] = extendedFormat.reads(json)
}
abstract class Entity {
var id: Option[Long] = None
}
With Play 2.3, I could then write
implicit val userFormat: Format[User] = new EntityFormat(Json.format[User])
Which would then work with with the ID-field being in the generated JSON. However, with Play 2.4 I'm getting following compile-time issues:
No Json formatter found for type Option[Long]. Try to implement an implicit Format for this type. (__ \ "id").format[Option[Long]]
missing arguments for method tupleToEntity in class DomainEntityFormat; follow this method with `_' if you want to treat it as a partially applied function )(tupleToEntity, entityToTuple)
^
missing arguments for method tupleToEntity in class DomainEntityFormat; follow this method with `_' if you want to treat it as a partially applied function )(tupleToEntity, entityToTuple)
^
How should you do the extending with Play 2.4 to make that kind of JSON Format work?
Instead of:
(__ \ "id").format[Option[Long]]
Try:
(__ \ "id").formatNullable[Long]

How to enforce strict serialization of JSON in Play 2.x

Play's JSON serialization is by default permissive when serializing from JSON into a case class. For example.
case class Stuff(name: String, value: Option[Boolean])
implicit val stuffReads: Reads[Stuff] = (
( __ \ 'name).read[String] and
( __ \ 'value).readNullable[Boolean]
)(Stuff.apply _)
If the following JSON was received:
{name: "My Stuff", value: true, extraField: "this shouldn't be here"}
It will succeed with a 'JsSuccess' and discard the 'extraField'.
Is there a way to construct the Json Reads function to have it return a JsError if there are 'unhandled' fields?
You can verify that the object doesn't contain extra keys before performing your own decoding:
import play.api.data.validation.ValidationError
def onlyFields(allowed: String*): Reads[JsObject] = Reads.filter(
ValidationError("One or more extra fields!")
)(_.keys.forall(allowed.contains))
Or if you don't care about error messages (and that one's not very helpful, anyway):
def onlyFields(allowed: String*): Reads[JsObject] =
Reads.verifying(_.keys.forall(allowed.contains))
And then:
implicit val stuffReads: Reads[Stuff] = onlyFields("name", "value") andThen (
(__ \ 'name).read[String] and
(__ \ 'value).readNullable[Boolean]
)(Stuff)
The repetition isn't very nice, but it works.
Inspired from Travis' comment to use LabelledGeneric I was able achieve compile time safe solution.
object toStringName extends Poly1 {
implicit def keyToStrName[A] = at[Symbol with A](_.name)
}
case class Foo(bar: String, boo: Boolean)
val labl = LabelledGeneric[Foo]
val keys = Keys[labl.Repr].apply
now keys.map (toStringName).toList will give you
res0: List[String] = List(bar, boo)