在 Scala 中使用单个类型别名将多个注释应用于 class and/or 字段

Apply multiple annotation to a class and/or field using a single type alias in Scala

看完scala.annotation.meta.field的用法https://www.scala-lang.org/api/current/scala/annotation/meta/index.html

由于我正在使用 Xstream,因此我目前需要为该字段设置注释 @XStreamAlias("alias") 以更改它们的反序列化方式。为简单起见,我使用类型别名:

import com.thoughtworks.xstream.annotations.XStreamAlias

type xStreamAlias = XStreamAlias @field

@XStreamAlias("a")
case class A(@xStreamAlias("B") b: String)

我在大约 40 类 中使用了它,所以为了简单起见,我也使用了一个包对象。

但是,为了兼容性和简单性,我现在想切换到 Jackson 并保留一个包含 Jackson 和 Xstream 的注释。我想做类似的事情:

import com.fasterxml.jackson.annotation.JsonProperty
import com.thoughtworks.xstream.annotations.XStreamAlias

type alias = XStreamAlias JsonProperty @field

@alias("a")
case class A(@alias("B") b: String)

我希望通过这种方式将 Jackson 和 XStream 合并为一个注释,因为它们将以相同的方式使用。

我的问题是:

  1. 这种用法看起来正确且可维护吗?
  2. 如果您看到上面的示例,我将此类注释仅用于字段而不是 类。即使是 类?
  3. 也能安全使用注解吗

我从未见过将 2 个注释与类型别名混合使用的用法,在我看来,这似乎是对可维护性和可读性的改进,以集中所选注释的行为。

我已经看到这个问题:Scala Type aliases for annotations但我不确定是否有新的方法可以做到这一点,回答的用户似乎对他们的解决方案不确定。

type xStreamAlias = XStreamAlias @field 的右侧是用 @field 注释的类型 XStreamAlias。所以 type alias = XStreamAlias JsonProperty @field 不可能是正确的语法。

您可以定义一个 macro annotation,它被扩展时使用适当的(相同的)参数打开 @XStreamAlias@JsonProperty。您可以定义如何处理 @alias 注释字段和 class 分开的情况。

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("Enable macro annotations")
class alias(name: String) extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro AliasMacro.impl
}

object AliasMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    val name = c.prefix.tree match {
      case q"new alias(${nme: String})" => nme        
    }

    val XStreamAlias = tq"_root_.com.thoughtworks.xstream.annotations.XStreamAlias"
    val JsonProperty = tq"_root_.com.fasterxml.jackson.annotation.JsonProperty"
    val fieldAnnotation = tq"_root_.scala.annotation.meta.field"

    annottees match {
      // @alias annotates field
      case q"$_ val $valTName: $_ = $_" ::
        q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" ::
        objectOrNil =>

        // switch both @XStreamAlias and @JsonProperty on
        val paramss1 = paramss.map(_.map {
          case q"$paramMods val $paramTName: $paramTpt = $paramExpr" if paramTName == valTName =>
            val paramMods1 = paramMods.mapAnnotations(annotations =>
              q"new ($XStreamAlias @$fieldAnnotation)($name)" ::
                q"new ($JsonProperty @$fieldAnnotation)($name)" ::
                annotations
            )
            q"$paramMods1 val $paramTName: $paramTpt = $paramExpr"

          case param => param
        })

        q"""
          $mods class $tpname[..$tparams] $ctorMods(...$paramss1) extends { ..$earlydefns } with ..$parents { $self => ..$stats }
          ..$objectOrNil
        """
          
      // @alias annotates class
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" ::
        objectOrNil =>

        // switch only @XStreamAlias on
        val mods1 = mods.mapAnnotations(annotations =>
          q"new $XStreamAlias($name)" :: annotations
        )

        q"""
          $mods1 class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }
          ..$objectOrNil
        """
    }
  }
}

用法:

@alias("a")
case class A(b0: Int, @alias("B") b: String, b1: Boolean)

//scalac: {
//  @new _root_.com.thoughtworks.xstream.annotations.XStreamAlias("a") case class A extends scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val b0: Int = _;
//    @new _root_.com.thoughtworks.xstream.annotations.XStreamAlias @_root_.scala.annotation.meta.field("B") @new _root_.com.fasterxml.jackson.annotation.JsonProperty @_root_.scala.annotation.meta.field("B") <caseaccessor> <paramaccessor> val b: String = _;
//    <caseaccessor> <paramaccessor> val b1: Boolean = _;
//    def <init>(b0: Int, @new _root_.com.thoughtworks.xstream.annotations.XStreamAlias @_root_.scala.annotation.meta.field("B") @new _root_.com.fasterxml.jackson.annotation.JsonProperty @_root_.scala.annotation.meta.field("B") b: String, b1: Boolean) = {
//      super.<init>();
//      ()
//    }
//  };
//  ()
//}