我在模型中使用了类型安全标签。
标签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 的Json.format
宏不能编译:
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
实例,那么一切顺利:
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))
答案 0 :(得分:4)
您可以将其称为错误,或者可能是缺少的功能。您可以使用编译器标志来查看生成的宏代码。 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
,并且您在宏扩展时会出现类型不匹配。
答案 1 :(得分:3)
Play-json格式宏不支持标记类型,但您可以自己为标记类型提供Json.format
。 (示例使用scalaz.Tag
,但其他实现的想法是相同的。)
如果您要标记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))
}
现在format
可以直接创建包含标记类型的每个case class
:
implicit val userFormat = Json.format[User]
答案 2 :(得分:2)
经过一番调查后发现问题是由于使用“递归”问题引起的。输入标签位置,例如:
case class User(userId: Long @@ User)
它既不适用于我的代码API也不适用于Shapeless&#39; (怀疑与Scalaz标签相同,但我没有尝试过)。
因此,我的解决方法是为标签使用单独的类型,例如:
sealed trait U
case class User(userId: Long @@ U)