How to avoid autocast provided by Play Framwork Json Reads, but get exception instead

Reading seems to do an auto-cast for me if casting is possible. for example Float -> Int. For example, if the code gets json like below,

{
"name": "Jack",
"age": 22.4,
"role": "Coder"
}

      

an instance of the Person class will be 22 years old instead of receiving an invalid argument. If I really want an exception in this case, what's the best solution? thanks a lot.

case class Person(val name: String, val age: Int, val role: String)

object Person {
implicit val residentReads: Reads[Resident] = (
  (JsPath \ "name").read[String](minLength[String](3)) and
  (JsPath \ "age").read[Int](min(0)) and
  (JsPath \ "role").readNullable[String]
)(Resident.apply _)

...
}

      

+3


source to share


1 answer


By looking at the source code, we can see why. Any value that matched a JsNumber

caused toInt

:

implicit object IntReads extends Reads[Int] {
    def reads(json: JsValue) = json match {
        case JsNumber(n) => JsSuccess(n.toInt)
        case _ => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsnumber"))))
    }
}

      

To avoid this, we can implement a new Reads[Int]

one that will not check for non-integers:

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import play.api.data.validation._

implicit object WholeIntReads extends Reads[Int] {
    def reads(json: JsValue) = json match {
        case JsNumber(n) if(n.isValidInt) => JsSuccess(n.toInt)
        case _ => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsnumber"))))
    }
}

      

And use it like this:



case class Person(val name: String, val age: Int, val role: String)

object Person {
    implicit val residentReads: Reads[Person] = (
        (JsPath \ "name").read[String](minLength[String](3)) and
        (JsPath \ "age").read[Int](WholeIntReads keepAnd min(0)) and
        (JsPath \ "role").read[String]
    )(Person.apply _)
}

      

(There were some inconsistencies in your example code, so I fixed them for compilation. The only relevant line is age

.)

Results:

scala> Json.parse("""{"name": "Jack","age": 22.4,"role": "Coder"}""").validate[Person]
res13: play.api.libs.json.JsResult[Person] = JsError(List((/age,List(ValidationError(error.expected.jsnumber,WrappedArray())))))

scala> Json.parse("""{"name": "Jack","age": 22,"role": "Coder"}""").validate[Person]
res14: play.api.libs.json.JsResult[Person] = JsSuccess(Person(Jack,22,Coder),)

scala> Json.parse("""{"name": "Jack","age": -22,"role": "Coder"}""").validate[Person]
res15: play.api.libs.json.JsResult[Person] = JsError(List((/age,List(ValidationError(error.min,WrappedArray(0))))))

      

Please note that I am using validate[T]

and not throwing exceptions as this is best practice. This will allow it to be ValidationError

accumulated and processed without any exceptions.

+4


source







All Articles