I have some models in a Play! application that I would like to serialize/deserialize to and from JSON. I used to have separate methods for that, but I have seen that the preferred way is to give an implicit instance of Formats[T]
or Reads[T]
, like
import play.api.libs.json.{ JsValue, Reads }
case class Foo(bar: Int, ...)
object Foo {
implicit object FooReads extends Reads[Foo] {
def reads(json: JsValue): Foo = //whatever
}
}
Now, it may happen that the model has the correct fields in the JSON, but it does not validate. In this case, I am not able to deserialize - I should get an exception when using json.as[Foo]
or None
when using json.asOpt[Foo]
.
If I throw an exception when I find a field that does not validate, everything seems to work as expected. But I took the care of trying to find out what exception I should throw, and in the source for JsValue
I found this
def asOpt[T](implicit fjs: Reads[T]): Option[T] = fjs.reads(this).fold(
valid = v => Some(v),
invalid = _ => None
).filter {
case JsUndefined(_) => false
case _ => true
}
Now, I cannot understand how this is supposed to work. The implicit instance of fjs
is supplied by myself in the companion object, so I know that fjs.reads(this)
either returns a Foo
or throws an exception.
Where is this fold
coming from? It certainly is not a method on Foo
. I guess one could have an implicit conversion, but it should be from Any
to something with a fold
method, so it could not be of much interest. Even worse, if fjs.reads(this)
throws an exception, there is nothing to catch it!
So, how should one handle invalid input in the JSON in an instance of
Reads[T]
? And how does the mechanism above actually work?
Looking at JsonValue.scala in Play 2.0.x:
def asOpt[T](implicit fjs: Reads[T]): Option[T] = catching(classOf[RuntimeException]).opt(fjs.reads(this)).filter {
case JsUndefined(_) => false
case _ => true
}
In fact the code is using scala.util.control.Exception.catching[T](exceptions: Class[_]*): Catch[T], which returns a Catch[T]
. Then it calls opt(...) on it. If an exception is thrown, then it will return a None instead of an instance of T
.
So, when you get an error deserializing, you can safely throw an exception.