首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用带标记类型的play-json格式/读/写宏

如何使用带标记类型的play-json格式/读/写宏
EN

Stack Overflow用户
提问于 2015-11-10 18:58:44
回答 3查看 890关注 0票数 3

我在我的模型中使用类型标签。

标签API:

代码语言:javascript
复制
/** Tag of type `U`. */
type Tag[+U] = { type Tag <: U }
/** Type `T` tagged with tag of type `U`. */
type @@[T, +U] = T with Tag[U]

implicit class Taggable[T](val t: T) extends AnyVal {
  /** Tag with type `U`. */
  def tag[U]: T @@ U = t.asInstanceOf[T @@ U]
  /** Tag with type `U`. */
  def @@[U]: T @@ U = tag[U]
}

示例模型:

代码语言:javascript
复制
case class User(id: Long @@ User, name: String)

问题是:在case类中有标记类型时,调用play的Json.format宏时不会编译:

代码语言:javascript
复制
import play.api.libs.json._

implicit val userIdFormat: Format[Long @@ User] = ???
Json.format[User] // doesn't compile

Error:(22, 14) type mismatch; found : id.type (with underlying type com.artezio.util.tags.User) required: com.artezio.util.tags.@@[Long,com.artezio.util.tags.User] (which expands to) Long with AnyRef{type Tag <: com.artezio.util.tags.User}

但是,如果我手动创建Format实例,一切都进展顺利:

代码语言:javascript
复制
import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val userIdFormat: Format[Long @@ User] = ???
implicit val userFormat: Format[User] = (
  (JsPath \ "id").format[Long @@ User] and
  (JsPath \ "name").format[String]
)(User.apply, unlift(User.unapply))
EN

回答 3

Stack Overflow用户

发布于 2015-11-11 02:29:08

您可以称它为bug,也可能称为缺少的功能。可以使用编译器标志查看宏的生成代码。scalacOptions ++= Seq("-Ymacro-debug-verbose")

您得到的是(使用Reads而不是Format):

代码语言:javascript
复制
{
  import play.api.libs.functional.syntax._;
  final class $anon extends play.api.libs.json.util.LazyHelper[Reads, User] {
    def <init>() = {
      super.<init>();
      ()
    };
    override lazy val lazyStuff: Reads[User] = play.api.libs.json.JsPath.$bslash("id").lazyRead(this.lazyStuff).and(play.api.libs.json.JsPath.$bslash("name").read(json.this.Reads.StringReads)).apply(((id, name) => User.apply(id, name)))
  };
  new $anon()
}.lazyStuff

简化为可读性:

代码语言:javascript
复制
{
  import play.api.libs.functional.syntax._
  final class $anon extends play.api.libs.json.util.LazyHelper[Reads, User] {
    override lazy val lazyStuff: Reads[User] = {
      (__ \ ("id")).lazyRead(this.lazyStuff)
      .and((__ \ ("name")).read(Reads.StringReads))
      .apply(((id, name) => User.apply(id, name)))    
    }

  }
  new $anon()
}.lazyStuff

注意递归调用(__ \ ("id")).lazyRead(this.lazyStuff),它是一个Reads[User]。由于某些原因,id被推断为宏中有类型User,这会导致调用错误的Reads,并且在宏展开时会得到类型不匹配。

票数 4
EN

Stack Overflow用户

发布于 2015-11-11 10:51:43

Play-json格式宏不支持标记类型,但您可以自己为标记类型提供Json.format。(示例正在使用scalaz.Tag,但idea在其他实现中是一样的)。

如果您正在标记Long类型,则可以使用以下方法:

代码语言:javascript
复制
implicit def taggedLongFormat[T]: Format[Long @@ T] = new Format[Long @@ T] {
  def reads(json: JsValue): JsResult[Long @@ T] = json match {
    case JsNumber(v) => JsSuccess(Tag.of[T](v.toLong))
    case unknown => JsError(s"Number value expected, got: $unknown")
  }
  def writes(v: Long @@ T): JsValue = JsNumber(Tag.unwrap(v))
}

或者,如果您正在标记String类型,则可以使用以下命令:

代码语言:javascript
复制
implicit def taggedStringFormat[T]: Format[String @@ T] = new Format[String @@ T] {
  def reads(json: JsValue): JsResult[String @@ T] = json match {
    case JsString(v) => JsSuccess(Tag.of[T](v))
    case unknown => JsError(s"String value expected, got: $unknown")
  }
  def writes(v: String @@ T): JsValue = JsString(Tag.unwrap(v))
}

现在,可以直接为每个包含标记类型的case class创建format

代码语言:javascript
复制
implicit val userFormat = Json.format[User]
票数 3
EN

Stack Overflow用户

发布于 2015-11-11 17:45:07

经过一些调查,发现问题是由于在标签位置使用“递归”类型引起的,例如:

代码语言:javascript
复制
case class User(userId: Long @@ User)

它既不适用于我的标签API,也不适用于无形状的标签‘(虽然我没有尝试,但对Scalaz标记也有同样的怀疑)。

因此,我的解决办法是为标记使用一个单独的类型,例如:

代码语言:javascript
复制
sealed trait U
case class User(userId: Long @@ U)
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33637484

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档