我在我的模型中使用类型标签。
标签API:
/** 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]
}示例模型:
case class User(id: Long @@ User, name: String)问题是:在case类中有标记类型时,调用play的Json.format宏时不会编译:
import play.api.libs.json._
implicit val userIdFormat: Format[Long @@ User] = ???
Json.format[User] // doesn't compileError:(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实例,一切都进展顺利:
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))发布于 2015-11-11 02:29:08
您可以称它为bug,也可能称为缺少的功能。可以使用编译器标志查看宏的生成代码。scalacOptions ++= Seq("-Ymacro-debug-verbose")
您得到的是(使用Reads而不是Format):
{
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简化为可读性:
{
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,并且在宏展开时会得到类型不匹配。
发布于 2015-11-11 10:51:43
Play-json格式宏不支持标记类型,但您可以自己为标记类型提供Json.format。(示例正在使用scalaz.Tag,但idea在其他实现中是一样的)。
如果您正在标记Long类型,则可以使用以下方法:
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类型,则可以使用以下命令:
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:
implicit val userFormat = Json.format[User]发布于 2015-11-11 17:45:07
经过一些调查,发现问题是由于在标签位置使用“递归”类型引起的,例如:
case class User(userId: Long @@ User)它既不适用于我的标签API,也不适用于无形状的标签‘(虽然我没有尝试,但对Scalaz标记也有同样的怀疑)。
因此,我的解决办法是为标记使用一个单独的类型,例如:
sealed trait U
case class User(userId: Long @@ U)https://stackoverflow.com/questions/33637484
复制相似问题