JsValue
has two methods
def validate[A](implicit rds: Reads[A]): JsResult[A]
- Tries to convert the node into a JsResult[T] (Success or Error).
def validateOpt[A](implicit rds: Reads[A]): JsResult[Option[A]]
- I suppose it also does the same thing.
In which situation would validateOpt
be used? In my opinion, if JsResult
fails then I'll get the error in JsError
. So what is the point of having an additional layer of Option
in JsSuccess
as JsSuccess
will always contain value after successful conversion of JsValue
into the type A
?
validateOpt
should be used when a null JSON value or missing JSON path is not considered an error. For example, say we have the following model
case class Person(
name: String
employer: Option[String]
)
where employer
field is optional as it is perfectly reasonable for a person not to be employed, whilst they always have a name. Then deserialising the following JSON
{
"name": "Picard"
}
should succeed even though employer
path is missing. Thus manually defining Reads[Person]
would make use of validateOpt
like so
implicit val personRead: Reads[Person] = Reads { json =>
for {
name <- (json \ "name").validate[String]
employer <- (json \ "employer").validateOpt[String]
} yield Person(name, employer)
}
Also contrast deserialisation of null
, for example
val raw = "null"
val json = Json.parse(raw)
println(json.validate[String])
println(json.validateOpt[String])
should output
JsError(List((,List(JsonValidationError(List(error.expected.jsstring),WrappedArray())))))
JsSuccess(None,)
where we see validateOpt
resulted in success.