Type parameter in play-json read / write wizard
I have a parameterized case class CaseClass[T](name: String, t: T)
for which I would like to have serialization / deserialization with play-json (2.5).
Of course, I cannot do this unless I have an equivalent for the type T
, so I define
object CaseClass {
implicit def reads[T: Reads] = Json.reads[CaseClass[T]]
}
But I am getting 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 a macro Json.writes
, I get the error
type mismatch;
found : CaseClass[Nothing] => (String, Nothing)
required: CaseClass[T] => (String, T)
The most amazing thing is that no error occurs when I use a macro Json.format
.
I know I have different solutions to get around this problem (using Json.format
, writing my serializer (de) manually, ...), but I'm very curious as to why this might be happening here.
source to share
This is either a macro limitation Json.reads
, type inference, or both. Type inference affects this a little, because you can see that something is being output as Nothing
in the error message.
If you use the compiler flag -Ymacro-debug-lite
, you can see the AST generated macro.
implicit def reads[T](implicit r: Reads[T]): Reads[CaseClass[T]] =
Json.reads[CaseClass[T]]
Translated into:
_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>)))
Removed, it looks like this:
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 no type parameter CaseClass.apply
is specified and is outputted as Nothing
. Manually adding T
to apply
fixes the problem, but the macro probably doesn't know what is important T
in CaseClass[T]
.
To take a closer look at the problem of type inference with combinators Reads
, we call FunctionalBuilder.CanBuild2#apply
which expects (A1, A2) => B
. But the compiler cannot deduce correctly A2
.
For Writes
there is a similar problem, where we need it B => (A1, A2)
, but the compiler cannot correctly determine B
or A2
(which corresponds to CaseClass[T]
and T
, respectively).
Format
requires both of the above functions, and the compiler can justify what it A2
should be T
.
source to share