play-jsonread/write宏中的类型参数

Type parameter in play-json read/write macro

我有一个参数化的案例 class CaseClass[T](name: String, t: T) 我想 serialization/deserialization 使用 play-json (2.5).

当然,如果我没有T类型的等价物,我就不能拥有这个,所以我定义了

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

但是我得到以下编译器错误:

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

如果我尝试对 Json.writes 宏执行相同的操作,我会收到错误消息

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

最令人惊讶的是,当我使用 Json.format 宏时,这两个错误都没有出现。

我知道我有不同的解决方案来绕过这个问题(使用 Json.format,手动编写我的(反)序列化程序,...),但我很好奇为什么会发生这种情况这里。

这是 Json.reads 宏和/或类型推断的限制。类型推断至少与它有一点关系,因为您可以在错误消息中看到某些东西被推断为 Nothing

如果你使用编译器标志-Ymacro-debug-lite,你可以看到宏生成的AST。

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

转换为:

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

清理后,看起来像:

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

不幸的是,它没有编译,因为 CaseClass.apply 的类型参数没有提供并且被推断为 Nothing。手动将 T 添加到 apply 可以解决问题,但宏可能不知道 CaseClass[T] 中的 T 很重要。

为了更详细地解决类型推断问题,我们使用 Reads 组合器调用 FunctionalBuilder.CanBuild2#apply,它需要一个 (A1, A2) => B。但是编译器无法正确推断 A2.

对于 Writes,存在类似的问题,我们需要一个 B => (A1, A2),但编译器无法正确推断 BA2(这是 CaseClass[T]T

Format 需要上述两个函数,编译器能够推断出 A2 必须是 T.