I have a dummy class Foo
which has three members:
import play.api.libs.json.Json
case class Foo(id: String, fooType: FooType, nextId: Option[String])
object Foo {
implicit val fooReads = Json.reads[Foo]
implicit val fooFormat = Json.format[Foo]
}
where FooType
is defined as
import play.api.libs.json.Json
case class FooType(a: String, b: String)
object FooType {
implicit val fooTypeReads = Json.reads[FooType]
implicit val fooTypeFormat = Json.format[FooType]
}
I find some interesting behavior when serializing a Foo
object. If I serialize a Foo
to JSON, parse the JSONified Foo, I find that all the members are parsed correctly:
val id = "id"
val fooType = FooType("a", "b")
val nextId = None
val foo = Foo(id, fooType, nextId)
val jsonFoo = Json.toJson(foo)
val parsedFoo = Json.parse(jsonFoo.toString).as[Foo]
assert(parsedFoo == foo)
assert(parsedFoo.id == id)
assert(parsedFoo.fooType == fooType)
assert(parsedFoo.nextId.isEmpty)
This is good, because that's what I expect.
However, in my next test, I find that the nextId
field is not serializable at all:
val id = "id"
val fooType = FooType("a", "b")
val nextId = None
val foo = Foo(id, fooType, nextId)
val jsonFoo = Json.toJson(foo)
assert((jsonFoo \ "id").as[String] == id)
assert((jsonFoo \ "fooType").as[FooType] == fooType)
assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)
This fails with the following error:
Error:(38, 35) No Json deserializer found for type Option[String]. Try to implement an implicit Reads or Format for this type.
assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)
Error:(38, 35) not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[Option[String]])Option[String].
Unspecified value parameter fjs.
assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)
Similarly, I find that when I print the JSON object dumped by Json.toJson(foo)
, the nextId
field is missing from the JSON object:
println(Json.prettyPrint(jsonFoo))
{
"id" : "id",
"fooType" : {
"a" : "a",
"b" : "b"
}
}
I can, however, parse the nextId
field with toOption
; i.e.,
assert((jsonFoo \ "nextId").toOption.isEmpty)
How can my object be correctly parsed from JSON if one of its members isn't deserializable natively?
The nextId
field is serializable, otherwise, you wouldn't have been able to write a Foo
to JSON at all.
jsonFoo: play.api.libs.json.JsValue = {"id":"id","fooType":{"a":"a","b":"b"}}
The problem you're having is that there are no Reads
for Option[A]
. Options are handled specially by Play JSON. When using JSON combinators, we use readNullable[A]
and writeNullable[A]
instead of read[Option[A]]
and write[Option[A]]
. Likewise, when using methods to pull individual fields from a JsValue
, calling as
will not work because it requires an implicit Reads[A]
for the type you give it (in this case a Reads[Option[String]]
, which does not exist).
Instead, you need to use asOpt
, which will correctly handle the Option
underneath:
scala> (jsonFoo \ "nextId").asOpt[String]
res1: Option[String] = None
nextId
doesn't appear in the printed JSON because the value you are serializing is None
. This is what is expected to happen. Since the value is optional, it gets omitted from the JSON (it's just undefined
in JavaScript). If it has a value, it will appear:
scala> Json.toJson(Foo("id", FooType("a", "b"), Option("abcdef")))
res3: play.api.libs.json.JsValue = {"id":"id","fooType":{"a":"a","b":"b"},"nextId":"abcdef"}
If something wasn't serializable with Play JSON, it simply wouldn't compile.