3 votes

Paramètre de type dans la macro de lecture/écriture play-json

J'ai une classe de cas paramétrée CaseClass[T](name: String, t: T) pour lequel je voudrais avoir une sérialisation/désérialisation en utilisant play-json (2.5).

Bien sûr, je ne peux pas avoir cela si je n'ai pas l'équivalent pour le type T donc je définis

object CaseClass {
  implicit def reads[T: Reads] = Json.reads[CaseClass[T]]
}

Mais j'obtiens l'erreur de compilation suivante :

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])

Si j'essaie de faire la même chose avec le Json.writes j'obtiens l'erreur suivante

type mismatch;
   found   : CaseClass[Nothing] => (String, Nothing)
   required: CaseClass[T] => (String, T)

Ce qui est le plus surprenant, c'est qu'aucune de ces erreurs ne se produit lorsque j'utilise la fonction Json.format macro.

Je sais que j'ai différentes solutions pour contourner ce problème (utilisation de Json.format , écrire mon (dé)sérialiseur à la main, ...), mais je suis plutôt curieux de savoir pourquoi cela peut se produire ici.

1voto

Michael Zajac Points 47215

C'est soit une limitation dans le Json.reads macro, inférence de type, ou les deux. L'inférence de type y est pour quelque chose au moins, car vous pouvez voir que quelque chose est inféré en tant que Nothing dans le message d'erreur.

Si vous utilisez l'option du compilateur -Ymacro-debug-lite vous pouvez voir la macro générée par AST.

implicit def reads[T](implicit r: Reads[T]): Reads[CaseClass[T]] = 
  Json.reads[CaseClass[T]]

Traduit à :

_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>)))

Nettoyé, on dirait :

implicit def reads[T](implicit w: Reads[T]): Reads[CaseClass[T]] = (
  (JsPath \ "name").read(Reads.StringReads) and
  (JsPath \ "t" ).read(r)
)(CaseClass.apply _)

Malheureusement, il ne compile pas parce que le paramètre type de l'option CaseClass.apply n'est pas fourni et est déduit comme Nothing . Ajout manuel T a apply corrige le problème, mais la macro ne sait probablement pas que T en CaseClass[T] est important.

Pour aborder la question de l'inférence de type de manière plus détaillée, avec l'option Reads nous appelons FunctionalBuilder.CanBuild2#apply qui attend un (A1, A2) => B . Mais le compilateur ne peut pas déduire correctement A2 .

Pour Writes il y a une question similaire, où nous avons besoin d'une B => (A1, A2) mais le compilateur est incapable de déduire B ou A2 correctement (ce qui est CaseClass[T] y T respectivement).

Format nécessite les deux fonctions ci-dessus, et le compilateur est capable de raisonner que A2 doit être T .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X