【问题标题】:play-json format macro fails for case classes with genericly typed properties具有通用类型属性的案例类的 play-json 格式宏失败
【发布时间】:2014-11-20 11:20:38
【问题描述】:

我不明白为什么 play-json 的 Json.format[] 宏无法处理以下案例类构造。更具体地说,编译器引发的错误并没有给我任何关于问题是什么以及如何解决它的线索。

给出以下定义:

trait Evt[T <: Evt[T]] {
  def meta: Meta[T]
}
case class Meta[T <: Evt[T]](value: String)
case class X(meta: Meta[X], data: String) extends Evt[X]

我尝试实现如下 JSON 格式。 Meta[X] 的格式只是初步尝试。我这里比较关心Json.format[X]的标准使用方式和下面描述的编译错误。

import play.api.libs.json._

object Formats {
  implicit val metaFormat = new Format[Meta[X]] {
    override def writes(o: Meta[X]): JsValue = JsString(o.value)
    override def reads(json: JsValue): JsResult[Meta[X]] = json.validate[String] match {
      case JsSuccess(value, path) => JsSuccess(Meta[X](value), path)
      case error: JsError => error
    }
  }
  implicit val xFormat: Format[X] = Json.format[X]
}

尝试编译它会产生以下错误:

[error] ...: type mismatch;
[error]  found   : X
[error]  required: Meta[X]
[error]   implicit val xFormat: Format[X] = Json.format[X]
[error]                                                ^

我想了解为什么会发生这种情况,并可能提供一些有关解决此问题的方法的线索,以便我可以格式化上述构造。

使用

  • Scala 2.11.4
  • play-json_2.11-2.3.6

更新

经过更多思考和挖掘,我怀疑编译器宏生成的代码会导致此编译错误,因为至少对我而言,错误消息与打印的代码行没有任何意义。我尝试使用 scalac 选项 -Ymacro-debug-lite 检查代码,这就是我得到的:

{
  import play.api.libs.functional.syntax._;
  final class $anon extends play.api.libs.json.util.LazyHelper[Format, X] {
    def <init>() = {
      super.<init>();
      ()
    };
    override lazy val lazyStuff: Format[X] = play.api.libs.json.JsPath.$bslash("meta").lazyFormat(this.lazyStuff).and(play.api.libs.json.JsPath.$bslash("data").format(json.this.Format.GenericFormat[String](json.this.Reads.StringReads, json.this.Writes.StringWrites))).apply(((meta, data) => X.apply(meta, data)), play.api.libs.functional.syntax.unlift(X.unapply))
  };
  new $anon()
}.lazyStuff

我认为$bslash("meta").lazyFormat(this.lazyStuff) 部分可能会导致编译错误,因为this.lazyStuff 返回一个Format[X],而在这个地方(对于路径meta)需要Format[Meta[X]]

通过这个分析,它看起来像是 format 宏的一个缺点。不过,我不确定这个分析是否正确,所以仍然希望得到一些澄清。

更新 2

更多的实验表明,这个问题不仅限于递归定义的类型。尝试对此构造使用 format 宏也会失败并出现相同的错误:

trait Evt[T] { 
  def meta: Meta[T]
}
case class Meta[T](value: String, anotherValue: String)
case class X(meta: Meta[X], data: String) extends Evt[X]

我发现了一些可能与宏 here 相关的工作。但是同样使用2.4.0-M1的play框架还是会出现同样的问题。

【问题讨论】:

    标签: json scala playframework playframework-2.3


    【解决方案1】:

    Json.format[A] 是一个用于序列化简单案例类的宏。我相信它在文档中的某处警告不要将它与递归定义的类型一起使用。它似乎可以使用 json 组合器:

    import play.api.libs.json._
    import play.api.libs.functional.syntax._
    
    implicit val XFormat: Format[X] = (
        (__ \ "meta").format[Meta[X]] and 
        (__ \ "data").format[String]
    )(X.apply _, unlift(X.unapply))
    
    
    scala> val x = X(Meta[X]("meta"), "data")
    x: X = X(Meta(meta),data)
    
    scala> Json.toJson(x)
    res1: play.api.libs.json.JsValue = {"meta":"meta","data":"data"}
    

    【讨论】:

    • 如果您指的是文档here 中的“递归类型”部分,那么它与case class Foo(childFoo: Foo) 等不同的情况有关。感谢您提供有关组合器的提示,但我真的很喜欢 format 宏在这里提供的简化。
    • @lost 宏仍然可能会卡住它。
    猜你喜欢
    • 1970-01-01
    • 2013-08-29
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    • 2017-04-25
    • 2011-06-30
    • 1970-01-01
    • 2015-06-27
    相关资源
    最近更新 更多