Context
I have a case class which is an item in a hierarchy, which refers to itself like so:
case class Node(
name: String,
children: Option[Seq[Node]] = None
)
I would like a PlayJson Format for this.
Usually, you can just do:
implicit lazy val formatter = Json.format[MyCaseClass]
But this doesn't work.
Why?
PlayJson uses a Scala macro to produce a Format for the case class, it will go through all fields, when it gets to the field children it will look for an existing formatter for Node which it hasn't constructed yet, ending with a compilation error:
No implicit format for Option[Seq[Node]] available.
[error] implicit lazy val formatter = Json.format[Node]
Questions
What's the best way to approach this?
Is this a known issue with PlayJson format macro?
This is something that can be found under recursive types in play-json docs:
import play.api.libs.functional.syntax._
import play.api.libs.json.{Reads, Writes, _}
case class Node(name: String, children: Option[Seq[Node]] = None)
implicit lazy val nodeReads: Reads[Node] = (
(__ \ "name").read[String] and
(__ \ "children").lazyReadNullable(Reads.seq[Node](nodeReads))
)(Node)
implicit lazy val nodeWrites: Writes[Node] = (
(__ \ "name").write[String] and
(__ \ "children").lazyWriteNullable(Writes.seq[Node](nodeWrites))
)(unlift(Node.unapply))
Since in that case Reads and Writes are symmetrical, you can create the whole thing as a single Format:
implicit lazy val nodeFormat: Format[Node] = (
(__ \ "name").format[String] and
(__ \ "children").lazyFormatNullable(Reads.seq[Node](nodeFormat), Writes.seq[Node](nodeFormat))
)(Node.apply, unlift(Node.unapply))
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]
Say I have to classes that I will be using it for read and write JSON
case class OrganizationData(var kind: Option[String],var id: Option[String],var organizationReference: OrganizationReferenceData,var objectHash: Option[String],var friendlyName: Option[String])
case class OrganizationReferenceData(var organizationId:String)
The following are the read and write
implicit val organizationWrites = (
(__ \ "kind").writeNullable[String] and
(__ \ "id").writeNullable[String] and
(__ \ "organizationReference").write[OrganizationReferenceData] and
(__ \ "objectHash").writeNullable[String] and
(__ \ "friendlyName").writeNullable[String]
) ( unlift( OrganizationData.unapply ) )
implicit val OrganizationReferenceDataWrites:Writes[OrganizationReferenceData] = (
(__ \ "organizationId").write[String]
) ( unlift( OrganizationReferenceData.unapply ) )
when I try to compile this I am getting the following error.
Error:(54, 34) 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 (config.core.OrganizationReferenceData => String)
(__ \ "organizationId").write[String]
^
Am I missing some thing here? One weird thing that I have seen is if I add another field in "OrganizationReferenceDataWrites" class and have the write element it compiles. SO if we cannot have a single element that what is the best practice to do it?
I'm not sure why you are getting that message, but an alternative way to do it may be:
implicit val OrganizationReferenceDataWrites = new Writes[OrganizationReferenceData] {
def writes(organizationReferenceData: OrganizationReferenceData) = Json.obj(
"organizationId" -> organizationReferenceData.organizationId)
}
Caveat: I don't know my combinators well enough to know if this is exactly equivalent.
BTW Are you using mutable (var) variables for a reason?
I have following case class:
case class Test(name: String, email: String, phone: String)
So in order to be able to serialize to JSON I wrote:
implicit val testWrites: Writes[Test] = (
(__ \ "name").write[String] and
(__ \ "email").write[String] and
(__ \ "phone").write[String]
)(unlift(Test.unapply))
I want to use this as something like DTO object, so I can exclude some fields while serizalizing.
Let's we say I want to show only name and email fields.
I tried something like this:
implicit val testWrites: Writes[Test] = (
(__ \ "name").write[String] and
(__ \ "email").write[String]
)(unlift(Test.unapply))
But this is giving me compile error -> Application does not take parameters.
Does anyone know what is the problem, and how I can achieve mentioned idea?
Play JSON combinators often take advantage of the unapply method that is automatically generated in the companion object of a case class.
For your case class:
case class Test(name: String, email: String, phone: String)
The unapply method looks like this:
def unapply(test: Test): Option[(String, String, String)] = Some((test.name, test.email, test.phone))
It returns the field values of the case class tupled and wrapped in Option. For example:
val test: Test = Test("John Sample", "fake#email.com", "1-800-NOT-NULL")
Test.unapply(test) // returns Some(("John Sample", "fake#email.com", "1-800-NOT-NULL"))
unlift transforms the unapply function into a PartialFunction[Test, (String, String, String)], which is then used to map an instance of Test to a tuple, which is then used to serialize the class.
You need not use Test.unapply. It's only convenient to use it when you want to serialize the entire class. If you only want some fields, you can define a similar function Test => Option[(String, String)]:
def simpleExtractor(test: Test): Option[(String, String)] = Some(test.name, test.email)
And then use it in the JSON combinators:
implicit val testWrites: Writes[Test] = (
(__ \ "name").write[String] and
(__ \ "email").write[String]
)(unlift(simpleExtractor))
Similarly, JSON Reads often takes advantage of the automatically generated apply method for case classes. Test.apply _ is a function (String, String, String) => Test-- basically the opposite as unapply, as you might have guessed. The JSON API assembles a tuple with the fields specified in the Reads, then passes that tuple through Test.apply _, which produces the deserialized Test.
To do generate a Reads that will only read two fields you could define another apply-like function:
def simpleBuilder(name: String, email: String): Test = Test(name, email, "default")
implicit val testReads: Reads[Test] = (
(__ \ "name").read[String] and
(__ \ "email").read[String]
)(unlift(simpleBuilder _))
Though personally I prefer not to do this, and define a default value within the Reads itself:
implicit val testReads: Reads[Test] = (
(__ \ "name").read[String] and
(__ \ "email").read[String] and
(__ \ "phone").read[String].orElse(Reads.pure("default"))
)(unlift(Test.apply _))
So, I've just recently started learning Scala. Sorry for my incompetence in advance.
I tried to look up my answer on stackoverflow. I was able to find several related topics, but I didn't spot my problem.
I'm trying to send a json response based on a Scala object. I have an Action and I'm doing the following:
def oneCredential = Action {
val cred = Credential("John", "Temp", "5437437")
Ok(Json.toJson(cred))
}
I've created a case class and appropriate implicit Writes[T] for it
import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.libs.json.util._
case class Credential(name: String, account: String, password: String)
object Credential{
implicit val credentialWrites = (
(__ \ "name").write[String] and
(__ \ "account").write[String] and
(__ \ "password").write[String]
)(Credential)
}
When I'm trying to run this, I've the following error: "Overloaded method value [apply] cannot be applied to (models.Credential.type)". Also, I tried this
implicit val credentialWrites = (
(__ \ "name").write[String] and
(__ \ "account").write[String] and
(__ \ "password").write[String]
)(Credential.apply _)
Fail. The error: could not find implicit value for parameter fu: play.api.libs.functional.Functor[play.api.libs.json.OWrites]
Then this:
implicit val credentialWrites = (
(__ \ "name").writes[String] and
(__ \ "account").writes[String] and
(__ \ "password").writes[String]
)(Credential)
Another fail: "value writes is not a member of play.api.libs.json.JsPath Note: implicit value credentialWrites is not applicable here because it comes after the application point and it lacks an explicit result type". Right, I understood the first part of an error, but not the second.
Finally I found a shorthand solution:
implicit val credentialWrites = Json.writes[Credential]
With this I've got no errors and the code finally worked. I've found the solution on this blog. It's said that the shorthand form is exactly the same as the one with "writes" above. But this "long" form didn't work for me.
Why is shorthand version working, while the long one isn't? Can somebody explain this?
Thank you!
PS Scala version: 2.10.2
The definitions you've given would work for Reads, but Writes needs a different kind of argument at the end. Take the following example:
case class Baz(foo: Int, bar: String)
val r = (__ \ 'foo).read[Int] and (__ \ 'bar).read[String]
val w = (__ \ 'foo).write[Int] and (__ \ 'bar).write[String]
r can be applied to a function (Int, String) => A to get a Reads[A], which means we can use it as follows (these are all equivalent):
val bazReader1 = r((foo: Int, bar: String) => Baz(foo, bar))
val bazReader2 = r(Baz.apply _)
val bazReader3 = r(Baz)
What we're doing is lifting the function into the applicative functor for Reads so that we can apply it to our Reads[Int] and Reads[String] (but you don't need to care about that if you don't want to).
w takes a different kind of argument (again, you don't need to care, but this is because Writes has a contravariant functor—it doesn't have an applicative functor):
val bazWriter1 = w((b: Baz) => (b.foo, b.bar))
We could write this equivalently as the following:
val bazWriter2 = w(unlift(Baz.unapply))
Here we're using the case class's automatically generated extractor, unapply, which returns an Option[(Int, String)]. We know in this case that it'll always return a Some, so we can use unlift (which comes from the functional syntax package, and just calls the standard library's Function.unlift) to turn the Baz => Option[(Int, String)] into the required Baz => (Int, String).
So just change your final line to )(unlift(Credential.unapply)) and you're good to go.
I'm trying to write a custom Json serializer in play for a case class but I don't want it to serialize all the fields of the class. I'm pretty new to Scala, so that is surely the problem but this is what I tried so far:
case class Foo(a: String, b: Int, c: Double)
Now the default way of doing this, as far as I saw in the examples is:
implicit val fooWrites: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int]
(__ \ "c").write[Double]
) (unlift(Foo.unapply))
But what if I want to omit "c" from the Json output? I've tried this so far but it doesn't compile:
implicit val fooWritesAlt: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int]
) (unlift({(f: Foo) => Some((f.a, f.b))}))
Any help is greatly appreciated!
If you are using Playframework 2.2 (not sure about earlier versions, but it should work as well) try this:
implicit val writer = new Writes[Foo] {
def writes(foo: Foo): JsValue = {
Json.obj("a" -> foo.a,
"b" -> foo.b)
}
}
What I usually do is convert a field to None and use writeNullable on it:
implicit val fooWrites: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int]
(__ \ "c").writeNullable[Double].contramap((_: Double) => None)
) (unlift(Foo.unapply))
Instead of specifying a combinator which produces an OWrites instance, one can directly construct an OWrites just as well:
val ignore = OWrites[Any](_ => Json.obj())
implicit val fooWrites: Writes[Foo] = (
(__ \ "a").write[String] and
(__ \ "b").write[Int] and
ignore
) (unlift(Foo.unapply))
This will ignore any value of the case class at that position and simply always return an empty JSON object.
This is very easy to do with the Play's JSON transformers:
val outputFoo = (__ \ 'c).json.prune
and then apply transform(outputFoo) to your existing JsValue:
val foo: Foo
val unprunedJson: JsValue = Json.toJson(foo)
unprunedJson.transform(outputFoo).map { jsonp =>
Ok(Json.obj("foo" -> jsonp))
}.recoverTotal { e =>
BadRequest(JsError.toFlatJson(e))
}
see here http://mandubian.com/2013/01/13/JSON-Coast-to-Coast/
What version of Play are you using? fooWritesAlt compiles for me using 2.1.3. One thing to note is that I needed to explicitly use the writes object to write the partial JSON object, i.e.
fooWritesAt.writes(Foo("a", 1, 2.0))
returns
{"a": "a", "b": 2}