3

我有一个参数化案例类CaseClass[T](name: String, t: T),我想使用 play-json (2.5) 对其进行序列化/反序列化。

当然,如果我没有 type 的等价物,我就不能拥有这个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,手动编写我的(反)序列化器,......),但我很好奇为什么会在这里发生。

4

1 回答 1

1

它要么是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. 手动添加Tapply修复问题,但宏可能不知道TinCaseClass[T]很重要。

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

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

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

于 2017-03-29T19:39:38.730 回答