Play JSON serialize/deserialize - json

I'm trying to go create some formats for this:
case class Work[T](todo: Seq[T], failed: Seq[T], success: Seq[T])
object Work {
implicit def format[T](implicit r: Reads[T], w: Writes[T]): Format[Work[T]] = Json.format[Work[T]]
}
object InternalMessage {
implicit def format[D, R](implicit
rD: Reads[D],
wD: Writes[D],
rR: Reads[R],
wR: Writes[R]
): Format[InternalMessage[D, R]] = Json.format[InternalMessage[D, R]]
}
case class InternalMessage[D, R](
download: List[Work[D]],
refine: List[Work[R]],
numberOfTries: Int
)
This doesn't work and I don't understand why. The error is
[error] /home/oleber/develop/data-platform/modules/importerTemplate/src/main/scala/template/TemplateModel.scala:46: No apply function found matching unapply parameters
[error] implicit def format[T](implicit r: Reads[T], w: Writes[T]): Format[Work[T]] = Json.format[Work[T]]
[error] ^
[error] /home/oleber/develop/data-platform/modules/importerTemplate/src/main/scala/template/TemplateModel.scala:55: No apply function found matching unapply parameters
[error] ): Format[InternalMessage[D, R]] = Json.format[InternalMessage[D, R]]
Thanks for any help

AFAIU you can't generate JSON Format for such data types using Play 2.5 macros. What is limiting you is that you use Seq[T] i.e. you want JSON serialization for a generic class that has fields that use some complex type built from that generic type rather than just raw generic T. Relevant code seems to be in the JsMacroImpl lines 135-141. In those lines inside paramsMatch inner method of maybeApply calculation the macro checks if there are an apply and unapply methods with matching (complement) signatures:
val maybeApply = applies.collectFirst {
case (apply: MethodSymbol) if hasVarArgs && {
// Option[List[c.universe.Type]]
val someApplyTypes = apply.paramLists.headOption.map(_.map(_.asTerm.typeSignature))
val someInitApply = someApplyTypes.map(_.init)
val someApplyLast = someApplyTypes.map(_.last)
val someInitUnapply = unapplyReturnTypes.map(_.init)
val someUnapplyLast = unapplyReturnTypes.map(_.last)
val initsMatch = someInitApply == someInitUnapply
val lastMatch = (for {
lastApply <- someApplyLast
lastUnapply <- someUnapplyLast
} yield lastApply <:< lastUnapply).getOrElse(false)
initsMatch && lastMatch
} => apply
case (apply: MethodSymbol) if {
val applyParams = apply.paramLists.headOption.
toList.flatten.map(_.typeSignature)
val unapplyParams = unapplyReturnTypes.toList.flatten
def paramsMatch = (applyParams, unapplyParams).zipped.forall {
case (TypeRef(NoPrefix, applyParam, _),
TypeRef(NoPrefix, unapplyParam, _)) => // for generic parameter
applyParam.fullName == unapplyParam.fullName
case (applyParam, unapplyParam) => applyParam =:= unapplyParam
}
(applyParams.size == unapplyParams.size && paramsMatch)
} => apply
}
As you can see in the relevant (non-hasVarArgs branch), this code only handles two cases: when the types in apply and unapply match exactly or when the same raw generic type is used. Your case of Seq[T] (as well as other complex generic types) is not handled here and the error you get is generated just a few lines down when maybeApply is empty:
val (tparams, params) = maybeApply match {
case Some(apply) => {
apply.typeParams -> apply.paramLists.head
// assume there is a single parameter group
}
case None => c.abort(c.enclosingPosition, "No apply function found matching unapply parameters")
}
In Play-JSON 2.6 this code was significantly re-worked and now it seems to support your case as well (see conforms inner method).
If upgrading to (not released yet) Play-JSON 2.6 is not acceptable, you can create Formats object(s) yourself using JSON Reads/Writes/Format Combinators doc. Something like this should work for you:
implicit def format[T](implicit r: Reads[T], w: Writes[T]): Format[Work[T]] = {
val workReads = (
(JsPath \ "todo").read[Seq[T]] and
(JsPath \ "failed").read[Seq[T]] and
(JsPath \ "success").read[Seq[T]]
) (Work.apply[T] _)
val workWrites = (
(JsPath \ "todo").write[Seq[T]] and
(JsPath \ "failed").write[Seq[T]] and
(JsPath \ "success").write[Seq[T]]
) (unlift(Work.unapply[T]))
new Format[Work[T]] {
override def reads(json: JsValue): JsResult[Work[T]] = workReads.reads(json)
override def writes(o: Work[T]): JsValue = workWrites.writes(o)
}
}

Related

Is JSON formatting not enough for parsing case class having > 22 fields?

My case class has 30 fields. For simplicity, I use 4 fields,
case class Person(id: Long, name: String, age: Int, sex: Sex)
val personFormat1: OFormat[(Long, String)] = ((__ \ "id").format[Long] ~ (__ \ "name").format[String]).tupled
val personFormat2: OFormat[(Int, Sex)] = ((__ \ "age").format[Int] ~ (__ \ "sex").format[Sex]).tupled
implicit val personFormat: Format[Person] = (personFormat1 ~ personFormat2)({
case ((id, name), (age, sex)) => new Person(id, name, age, sex)
}, (person: Format) => ((person.id, person.name), (person.age, person.sex)))
But even after writing formatter with format1 as a group of 22 fields and format2 as a group of 8 fields, I get error when am trying to parse the json of this case class.
Error is
No Json serializer as JsObject found for type Person. Try to implement an implicit OWrites or OFormat for this type.
How to write implicit Owrites or OFormat? or how to fix this issue?
I use Play-Json extensions library for working with JSON with more than 22 fields: https://github.com/xdotai/play-json-extensions
libraryDependencies += "ai.x" %% "play-json-extensions" % "0.8.0"
You need to an implicit writer to do this. Something like this
implicit val person= Json.format[Person]
Also, if you are using custom data types, like for your case Sex you need to specify a reader and writer. You do not need to do this for primitive types like Int, Long, String Etc.
def enumReads[T <: Enum[T]](mkEnum: String => T) = new Reads[T] {
override def reads(json: JsValue): JsResult[T] =
json match {
case JsString(s) => try {
JsSuccess(mkEnum(s))
}
catch {
case e: IllegalArgumentException =>
JsError("Not a valid enum value: " + s)
}
case v => JsError("Can't convert to enum: " + v)
}
}
implicit val enumWrites = new Writes[Enum[_]] {
def writes(e: Enum[_]) = JsString(e.toString)
}
implicit val sex = enumReads(Sex.valueOf)
Also, upgrade to scala 2.11 or later to avoid the limitation of 22 fields in case class. For more info see here: How to get around the Scala case class limit of 22 fields?
I found this using google, and this worked greatly for me
// https://mvnrepository.com/artifact/com.chuusai/shapeless_2.11
libraryDependencies += "com.chuusai" % "shapeless_2.11" % "2.3.2"
// https://mvnrepository.com/artifact/org.julienrf/play-json-derived-codecs_2.11
libraryDependencies += "org.julienrf" % "play-json-derived-codecs_2.11" % "3.2"

Overloaded method value [read] cannot be applied to (String => SearchController.this.TrackSearch)

I am querying the spotify api for a list of tracks for a given query using ws, when I come to transform the JSON data into a case class I'm getting an error that I've yet to figure out...
class SearchController #Inject() (
val ws: WSClient
) extends Controller {
case class TrackSearch(href: String)
implicit val trackResultsReads: Reads[TrackSearch] = (
(__ \ "tracks" \ "href").read[String]
)(TrackSearch.apply _)
def index = Action.async { implicit request =>
search("track", param(request, "q")).map { r =>
val ts = r.json.as[TrackSearch]
println(ts)
Ok
}
}
private def search(category: String, query: String): Future[Try[WSResponse]] = {
ws.url("https://api.spotify.com/v1/search")
.withQueryString("q" -> query, "type" -> category)
.get()
.map(Success(_))
.recover { case x => Failure(x) }
}
private def param(request: Request[AnyContent], name: String): String = {
request.queryString.get(name).flatMap(_.headOption).getOrElse("")
}
}
The error I am getting is:
Overloaded method value [read] cannot be applied to (String => SearchController.this.TrackSearch)
implicit val trackResultsReads: Reads[TrackSearch]
> (__ \ "tracks" \ "href").read[String]
)(TrackSearch.apply _)
If I query the JSPath in my action, I can get the "href" string back fine, so it is not that:
println(r._2.json \ "tracks" \ "href")
The issue is that there is just a single field. If you added a second field it would compile. I don't fully understand why it shouldn't compile with a single field. So in the single field case, try the following:
implicit val trackResultsReads: Reads[TrackSearch] = {
((__ \ "tracks" \ "href").read[String])
.map(TrackSearch(_))
}
Here is quite an old link where I found the above. See also this link for a similar SO question with a different approach.

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 ?

parse json string to a scala object

I use play framework version 2.2.1 with scala.
I have a large json-string that i wish to construct an object from.
I have written the formatters for the classes.
My question is what are the steps i am supposed to cast that string to concrete class.
e.g of code that i've tried:
val something = scala.util.parsing.json.JSON.parseFull(jsonContent).asInstanceOf[HsmTenant] //This has stucked my debug session and operation never completes
Or
val concreteClass = Json.parse(jsonContent).asInstanceOf[T] //message: "Error processing request - Failed to migrate, Exception=play.api.libs.json.JsObject cannot be cast to migration.hsm.HsmTenant"
When trying:
Json.parse(jsonContent).as[T]
I get the following:
Multiple markers at this line
- No Json deserializer found for type migration.hsm.HsmTenant. Try to implement an implicit Reads or Format for this type.
- not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[migration.hsm.HsmTenant])migration.hsm.HsmTenant. Unspecified value
parameter fjs.
- No Json deserializer found for type migration.hsm.HsmTenant. Try to implement an implicit Reads or Format for this type.
- not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[migration.hsm.HsmTenant])migration.hsm.HsmTenant. Unspecified value
parameter fjs.
- Line breakpoint:HsmParser [line: 16] - parse(jsonContent: String): migration.hsm.HsmTenant
My read and writes looks like: and they compile:
trait HsmFormats {
implicit val hsmRetailerFormat = Json.format[Retailer]
implicit val hsmproductFormat = new Format[HsmProduct]
{
def writes(item: HsmProduct): JsValue =
{
val retailers = item.r.getOrElse(List[Retailer]())
val images = item.images.getOrElse(List[String]())
Json.obj(
"u" -> item.u,
"s" -> item.s,
"z" -> item.z,
"n" -> item.n,
"v" -> item.v,
"vu" -> item.vu,
"t" -> item.t,
"r" -> retailers,
"images" -> images
)
}
def reads(json: JsValue): JsResult[HsmProduct] =
{
val retailers = (json \ "r").as[Option[List[Retailer]]]
var retaliersReal:Option[List[Retailer]] = None
if (retailers.isDefined)
{
retaliersReal = Some(retailers.get)
}
val images = (json \ "images").as[Option[List[String]]]
var imagesReal:Option[List[String]] = None
if (images.isDefined)
{
imagesReal = Some(images.get)
}
JsSuccess(new HsmProduct(
(json \ "u").as[String],
(json \ "s").as[Int],
(json \ "z").as[Int],
(json \ "n").as[String],
(json \ "v").as[String],
(json \ "vu").as[String],
(json \ "t").as[String],
retailers,
imagesReal
))
}
}
implicit val hsmTenantFormat = new Format[HsmTenant]
{
def writes(tenant: HsmTenant): JsValue =
{
val items = tenant.items.getOrElse(List[HsmProduct]())
Json.obj(
"items" -> tenant.items,
"prefixAndroid" -> tenant.prefixAndroid,
"prefixIOS" -> tenant.prefixIOS,
"er" -> tenant.er,
"erMessage" -> tenant.erMessage
)
}
def reads(json: JsValue): JsResult[HsmTenant] =
{
val items = (json \ "items").as[Option[List[HsmProduct]]]
var itemsReal:Option[List[HsmProduct]] = None
if (items.isDefined)
{
itemsReal = Some(items.get)
}
JsSuccess(new HsmTenant(
itemsReal,
(json \ "prefixAndroid").as[String],
(json \ "prefixIOS").as[String],
(json \ "er").as[Int],
(json \ "erMessage").as[String]
))
}
}
This should work if your Formats are correct:
import play.api.libs.json.Json
Json.parse(jsonContent).as[T]
What was the problem ?
It was not issue if import the Formats class, my format is a trait, so i must extend that trait in classes i use it.

Outputting 'null' for Option[T] in play-json serialization when value is None

I'm using play-json's macros to define implicit Writes for serializing JSON. However, it seems like by default play-json omits fields for which Option fields are set to None. Is there a way to change the default so that it outputs null instead? I know this is possible if I define my own Writes definition, but I'm interested in doing it via macros to reduce boilerplate code.
Example
case class Person(name: String, address: Option[String])
implicit val personWrites = Json.writes[Person]
Json.toJson(Person("John Smith", None))
// Outputs: {"name":"John Smith"}
// Instead want to output: {"name":"John Smith", "address": null}
The Json.writes macro generates a writeNullable[T] for optional fields. Like you know (or not), writeNullable[T] omits the field if the value is None, whereas write[Option[T]] generates a null field.
Defining a custom writer is the only option you have to get this behavior.
(
(__ \ 'name).write[String] and
(__ \ 'address).write[Option[String]]
)(unlift(Person.unapply _))
You can use a custom implicit JsonConfiguration, see Customize the macro to output null
implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
implicit val personWrites = Json.writes[Person]
Json.toJson(Person("John Smith", None))
Not a real solution for you situation. But slightly better than having to manually write the writes
I created a helper class that can "ensure" fields.
implicit class WritesOps[A](val self: Writes[A]) extends AnyVal {
def ensureField(fieldName: String, path: JsPath = __, value: JsValue = JsNull): Writes[A] = {
val update = path.json.update(
__.read[JsObject].map( o => if(o.keys.contains(fieldName)) o else o ++ Json.obj(fieldName -> value))
)
self.transform(js => js.validate(update) match {
case JsSuccess(v,_) => v
case err: JsError => throw new JsResultException(err.errors)
})
}
def ensureFields(fieldNames: String*)(value: JsValue = JsNull, path: JsPath = __): Writes[A] =
fieldNames.foldLeft(self)((w, fn) => w.ensureField(fn, path, value))
}
so that you can write
Json.writes[Person].ensureFields("address")()
Similar answer to above, but another syntax for this:
implicit val personWrites = new Writes[Person] {
override def writes(p: Person) = Json.obj(
"name" -> p.name,
"address" -> p.address,
)
}
This is simple:
implicit val personWrites = new Writes[Person] {
override def writes(p: Person) = Json.obj(
"name" -> p.name,
"address" -> noneToString(p.address),
)
}
def optToString[T](opt: Option[T]) =
if (opt.isDefined) opt.get.toString else "null"
You can define something like this :
implicit class JsPathExtended(path: JsPath) {
def writeJsonOption[T](implicit w: Writes[T]): OWrites[Option[T]] = OWrites[Option[T]] { option =>
option.map(value =>
JsPath.createObj(path -> w.writes(value))
).getOrElse(JsPath.createObj(path -> JsNull))
}
}
And if you are using play framework :
implicit val totoWrites: Writes[Toto] = (
(JsPath \ "titre").write[String] and
(JsPath \ "option").writeJsonOption[String] and
(JsPath \ "descriptionPoste").writeNullable[String]
) (unlift(Toto.unapply))
implicit val totoReads: Reads[Toto] = (
(JsPath \ "titre").read[String] and
(JsPath \ "option").readNullable[String] and
(JsPath \ "descriptionPoste").readNullable[String]
) (Toto.apply _)
You may wrap your option and redefine serialization behavior:
case class MyOption[T](o: Option[T])
implicit def myOptWrites[T: Writes] = Writes[MyOption[T]](_.o.map(Json.toJson).getOrElse(JsNull))
CAVEATS:
1)this approach is good only for case classes used solely as a serialization protocol definition. If you are reusing some data model classes in controllers - define custom serializers for them not to pollute the model.
2)(related only to Option) if you use the same class for writes and reads. Play will require the wrapped fields to be present (possibly null) during deserialization.
P.S.: Failed to do the same with type tagging. Compiler error is like
No instance of play.api.libs.json.Writes is available for tag.<refinement> (given the required writes were explicitly defined).
Looks like Play's macro fault.