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.

+3


source to share


1 answer


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

.

+1


source







All Articles