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]
Related
I am trying to marshall and un-marshall an Option[String] field to and from JSON. For my use-case, a None value should be marshaled as "null". Here is the code I have:
import org.scalatest.{FlatSpec, Matchers}
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
case class Person(
id: Int,
firstName: Option[String],
lastName: Option[String]
)
object Person {
implicit lazy val personFormat = (
(__ \ "id").format[Int] and
(__ \ "first_name").format[Option[String]] and
(__ \ "last_name").format[Option[String]]
)(Person.apply, unlift(Person.unapply))
}
class PersonSpec extends FlatSpec with Matchers {
"When Person instance is marshaled None fields " should
"be serialized as \"null\" values" in {
val person = Person(1, None, None)
import Person._
val json = Json.toJson(person)
println(json)
(json \ "id").as[Int] should be (1)
(json \ "first_name").get should be (JsNull)
(json \ "last_name").get should be (JsNull)
}
}
This results in the following compiler error:
PersonSpec.scala:19: No Json formatter found for type Option[String]. Try to implement an implicit Format for this type.
[error] (__ \ "first_name").format[Option[String]] and
[error] ^
These are some of the things I have tried:
Replacing (__ \ "first_name").format[Option[String]] with (__ \ "first_name").formatNullable[String] makes the compiler happy, but the test fails (""java.util.NoSuchElementException: None.get"") with the following output (from println(json))
{"id":1}
This confirms with formatNullable's behavior (don't render None valued fields).
Next, I replaced the format with a writes. Like so:
object Person {
implicit lazy val personWrite = (
(__ \ "id").write[Int] and
(__ \ "first_name").write[Option[String]] and
(__ \ "last_name").write[Option[String]]
)(unlift(Person.unapply))
}
Now, the compiler is happy and the test passes.
But I now need to implement a separate Reads. If I could, I would rather not as it violates DRY principle.
What am I doing wrong and when write[Option[...]] works perfectly why not format[Option[...]]?
Adding this code so that it is implicit-visible from your PersonFormat will make it work.
implicit def optionFormat[T: Format]: Format[Option[T]] = new Format[Option[T]]{
override def reads(json: JsValue): JsResult[Option[T]] = json.validateOpt[T]
override def writes(o: Option[T]): JsValue = o match {
case Some(t) ⇒ implicitly[Writes[T]].writes(t)
case None ⇒ JsNull
}
}
I think that in play it is assumed that option-valued fields should be treated optional at all, hence the behaviour you observed with formatNullable.
You can use:
(__ \ "first_name").formatNullable[String]
Trying to write a json formatter for this case class
case class OptionRange[+T](start: Option[T], end: Option[T])
This is what I have so far
implicit def fmt[T <: OptionRange[_]](implicit fmt: Format[Option[T]]): Format[OptionRange[T]] = new Format[OptionRange[T]] {
def reads(json: JsValue): JsSuccess[OptionRange[T]] = JsSuccess(new OptionRange[T] (
(json \ "start").as[Option[T]],
(json \ "end").as[Option[T]]
))
def writes(i: OptionRange[T]) = JsObject(Seq(
"start" -> Json.toJson(i.start),
"end" -> Json.toJson(i.end)
))
}
This piece of code compiles but when I try to format an OptionRange[Int] I get an error that there is no implicit format available.
How can I write a format that will be available in the implicit scope?
You have a bit of a cyclical type definition going on.
It looks to me like you are intending T to be evaluated as Int in this case. However, in the function definition, you've constrained [T <: OptionRange[_]]. So, Scala thinks that T must be an OptionRange of something.
This gets further complicated when you go to your implicit parameter for the function, (implicit fmt: Format[Option[T]]). If T is OptionRange[_], then you are telling the compiler to require a Format[Option[OptionRange[_]]] instead of a Format[Option[_]]. Your function can't be the source of that Format, since it can't be evaluated in order to provide the implicit required.
The solution is to stop constraining T.
implicit def fmt[T](implicit fmt: Format[Option[T]]) ...
Then, when you try to format an OptionRange as json:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> case class OptionRange[+T](start: Option[T], end: Option[T])
defined class OptionRange
scala> implicit def fmt[T](implicit fmt: Format[Option[T]]): Format[OptionRange[T]] = new Format[OptionRange[T]] {
| def reads(json: JsValue): JsSuccess[OptionRange[T]] = JsSuccess(new OptionRange[T] (
| (json \ "start").as[Option[T]],
| (json \ "end").as[Option[T]]
| ))
| def writes(i: OptionRange[T]) = JsObject(Seq(
| "start" -> Json.toJson(i.start),
| "end" -> Json.toJson(i.end)
| ))
| }
fmt: [T](implicit fmt: play.api.libs.json.Format[Option[T]])play.api.libs.json.Format[OptionRange[T]]
scala> Json.toJson(OptionRange(Some(1), Some(2)))
res0: play.api.libs.json.JsValue = {"start":1,"end":2}
Here is a way to do this using play-framework 2.3.9, Scala 2.11.8:
import play.api.libs.json._ // JSON library
import play.api.libs.json.Reads._ // Custom validation helpers
import play.api.libs.functional.syntax._ // Combinator syntax
case class OptionRange[+T](start: Option[T], end: Option[T])
object Hello extends App {
implicit def formatOptionRange[T](implicit formatT: Format[T]): Format[OptionRange[T]] =
(
(JsPath \ "start").formatNullable[T] and
(JsPath \ "end").formatNullable[T]
)(OptionRange.apply, unlift(OptionRange.unapply))
println(Json.toJson(OptionRange(Some(1), Some(2))))
}
//Prints:
//{"start":1,"end":2}
Here is the documentation on format.
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 ?
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.
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)))
}