在play-json读/写宏中键入参数

时间:2017-03-29 08:41:14

标签: scala scala-macros play-json

我有一个参数化的案例类CaseClass[T](name: String, t: T),我希望使用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,手工编写我的(de)序列化程序,...),但我很好奇为什么会发生这种情况这里。

1 个答案:

答案 0 :(得分: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。手动将T添加到apply会修复此问题,但宏可能不知道T中的CaseClass[T]是重要的。

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

对于Writes,存在类似问题,我们需要B => (A1, A2),但编译器无法正确推断BA2({{1}分别是}和CaseClass[T]

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