I have a parametrized case class CaseClass[T](name: String, t: T) for which I would like to have serialization/deserialization using play-json (2.5).
Of course, I cannot have this if I do not have the equivalent for the type T, so I define
object CaseClass {
implicit def reads[T: Reads] = Json.reads[CaseClass[T]]
}
But I get the following compiler error:
overloaded method value apply with alternatives:
[B](f: B => (String, T))(implicit fu: play.api.libs.functional.ContravariantFunctor[play.api.libs.json.Reads])play.api.libs.json.Reads[B] <and>
[B](f: (String, T) => B)(implicit fu: play.api.libs.functional.Functor[play.api.libs.json.Reads])play.api.libs.json.Reads[B]
cannot be applied to ((String, Nothing) => CaseClass[Nothing])
If I try to do the same with the Json.writes macro, I get the error
type mismatch;
found : CaseClass[Nothing] => (String, Nothing)
required: CaseClass[T] => (String, T)
What is most surprising, is that neither error occur when I use the Json.format macro.
I know I have different solutions to by-pass this problem (using Json.format, writing my (de)serializer by hand, ...), but I'm rather curious about why this can occur here.
It's either a limitation in the Json.reads macro, type inference, or both. Type inference has a little bit to do with it at least, because you can see that something is being inferred as Nothing in the error message.
If you use the compiler flag -Ymacro-debug-lite, you can see the macro generated AST.
implicit def reads[T](implicit r: Reads[T]): Reads[CaseClass[T]] =
Json.reads[CaseClass[T]]
Translates to:
_root_.play.api.libs.json.JsPath.$bslash("name").read(json.this.Reads.StringReads)
.and(_root_.play.api.libs.json.JsPath.$bslash("t").read(r))
.apply((CaseClass.apply: (() => <empty>)))
Cleaned up, it looks like:
implicit def reads[T](implicit w: Reads[T]): Reads[CaseClass[T]] = (
(JsPath \ "name").read(Reads.StringReads) and
(JsPath \ "t" ).read(r)
)(CaseClass.apply _)
Unfortunately, it doesn't compile because the type parameter of CaseClass.apply isn't supplied and is inferred as Nothing. Manually adding T to apply fixes the issue, but the macro likely doesn't know that T in CaseClass[T] is important.
To address the type inference issue with more detail, with the Reads combinators, we're calling FunctionalBuilder.CanBuild2#apply, which expects a (A1, A2) => B. But the compiler cannot properly infer A2.
For Writes, there is a similar issue, where we need a B => (A1, A2), but compiler is unable to infer B or A2 correctly (which is CaseClass[T] and T, respectively).
Format requires both of the above functions, and the compiler is able to reason that A2 must be T.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With