Play2.1 JSON Format with Empty String Validation - json

I am attempting to validate JSON when unmarshalling to an object in Play2.1. The Format object I have defined only validates when a field is absent in the JSON, but I want to validate that fields are nonEmpty strings. Is this possible? I've tried specifying the minLength() constraint (as seen here) in the reads() call, but I get a compiler error saying minLength can't be found. Is that only for the tuple approach?
See the following Specs2 Junit test which fails now, but should pass when the constraint is defined properly:
import org.specs2.mutable._
import play.api.libs.json._
class SimpleValidation extends SpecificationWithJUnit{
private val badPayload: JsValue = Json.obj(
"simpleValue1" -> "mySimpleValue", // Comment this line out to pass test
"simpleValue2" -> ""
)
"An IssueFormat" should {
"validate when unmarshalling" in {
badPayload.validate[SimpleObj].fold(
valid = (res => {
// Fail if valid
failure("Payload should have been invalid")
}),
invalid = (e => {
// Should be one error
e.length mustBeEqualTo(1)
}))
}
}
}
import play.api.libs.functional.syntax._
case class SimpleObj(simpleValue1: String, simpleValue2: String)
object SimpleObj {
val simpleReads = (
(__ \ "simpleValue1").read[String] and
(__ \ "simpleValue2").read[String])(SimpleObj.apply _) // read[String](minLength(0)) yields compiler error
val simpleWrites = (
(__ \ "simpleValue1").write[String] and
(__ \ "simpleValue2").write[String])(unlift(SimpleObj.unapply))
implicit val simpleFormat: Format[SimpleObj] = Format(simpleReads, simpleWrites)
}

You can use the minLength in Reads:
import play.api.libs.json.Reads._
Then minLength should be available, however, try this format instead:
implicit val simpleReads = (
(__ \ "simpleValue1").read(minLength[String](1)) and
(__ \ "simpleValue2").read(minLength[String](1))(SimpleObj.apply _)

After looking through the Play2.1 documentation some more, I was able to add a custome read validator. If you replace the SimpleObj from the original question, with the following, the test case will pass. Not sure if there is a simpler way to do this, but this definitely works:
object SimpleObj {
// defines a custom reads to be reused
// a reads that verifies your value is not equal to a give value
def notEqual[T](v: T)(implicit r: Reads[T]): Reads[T] = Reads.filterNot(ValidationError("validate.error.unexpected.value", v))(_ == v)
implicit val simpleReads = (
(__ \ "simpleValue1").read[String](notEqual("")) and
(__ \ "simpleValue2").read[String](notEqual("")))(SimpleObj.apply _)
val simpleWrites = (
(__ \ "simpleValue1").write[String] and
(__ \ "simpleValue2").write[String])(unlift(SimpleObj.unapply))
implicit val simpleFormat: Format[SimpleObj] = Format(simpleReads, simpleWrites)
}

Related

No Json formatter for Option[String]?

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]

Write JSon deserialiser (Read[T]) for play 2.1.x

Play documentation has following example for creating a Read[T] for a case class. It returns T or throws an exception.
In 2.1.x, it is recommended that JsResult[T] is returned. It should either have T or all the errors. It is also recommended to use JsPath. I am unable to write the reads code for 2.1.x.
This is 2.0.x code from play documentation
case class Creature(
name: String,
isDead: Boolean,
weight: Float
)
In Play2.0.x, you would write your reader as following:
import play.api.libs.json._
implicit val creatureReads = new Reads[Creature] {
def reads(js: JsValue): Creature = {
Creature(
(js \ "name").as[String],
(js \ "isDead").as[Boolean],
(js \ "weight").as[Float]
)
}
}
For 2.1.x, I guess I'll have to do something like
implicit val creatureReads = new Reads[Creature] {
def reads(js: JsValue): JsResult[Creature] = {
(JsPath \ "key1").read[String]
/* at this point, I should either have key1's value or some error. I am clueless how to distinguish between the two and keep processing the rest of the JSon, accumulating all values or errors.*/
}
}
Explanation is in the documentation:
https://www.playframework.com/documentation/2.2.1/ScalaJsonCombinators
http://mandubian.com/2012/09/08/unveiling-play-2-dot-1-json-api-part1-jspath-reads-combinators/
So you reads in 2.1 could be written as
implicit val creatureReads: Reads[Creature] = (
(__ \ "name").read[String] and
(__ \ "isDead").read[Boolean] and
(__ \ "weight").read[Float]
)(Creature)

PlayFramework in Scala, Reads - error in case of unknown key? [duplicate]

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)

case class to json (partial) conversion

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

Custom Json Writes with combinators - not all the fields of the case class are needed

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}