Scala | 如何将这段代码放入宏注解中?

huangapple go评论84阅读模式
英文:

Scala | How can this code be put into a macro annotation?

问题

我有许多包装类,它们的作用只是添加类型安全性和格式化。除了 ClassName 之外,每个包装类本质上都是相同的。

import com.helpers.buildFormatter
import spray.json.JsonFormat

final case class ClassName(value: String)

object ClassName {
  implicit val jsonFormatter: JsonFormat[ClassName] =
    buildFormatter[ClassName]("ClassName", _.value, this.apply)
}

有没有办法进一步缩短这个代码?我想知道是否可以使用注解、宏或继承来实现。

编辑:buildFormatter 提供了自定义的 JSON 解析,这就是为什么我希望在结果中保留它。

def buildStringFormatter[A](className: String, serializer: A => String, deserializer: String => A): JsonFormat[A]

从评论中:
问:包装 case 类是否始终只有一个参数,参数类型是否始终为 String,参数名称是否始终为 value
答:你是对的,只会有一个参数,参数名会是 value。我对限制这些有没有问题。

英文:

I have many wrapper classes which all they do is add type safety plus formatting. Besides the ClassName, each wrapper class is essentially the same.

import com.helpers.buildFormatter
import spray.json.JsonFormat

final case class ClassName(value: String)

object ClassName {
  implicit val jsonFormatter: JsonFormat[ClassName] =
    buildFormatter[ClassName]("ClassName", _.value, this.apply)
}

Is there a way to shorten this further? I'm wondering if an annotation, macro, or inheritance might work.

EDIT: buildFormatter provides custom json parsing which is why I'd like to keep it in the resulting answer

def buildStringFormatter[A](className: String, serializer: A => String, deserializer: String => A): JsonFormat[A]

From comments:
Q: Will wrapper case classes always be single-parameter, will parameter type always be String, will parameter name always be value?
A: You are correct, there would only be 1 parameter and the parameter name be value. I'm fine with restricting those restrictions

答案1

得分: 3

你可以谷歌搜索 spray json derivation

https://github.com/driver-oss/spray-json-derivation

https://github.com/milessabin/spray-json-shapeless

https://github.com/zackangelo/spray-json-macros

https://github.com/ExNexu/spray-json-annotation

等等。


由于 buildFormatter 的签名是

def buildFormatter[T](str: String, value: T => String, apply: String => T): JsonFormat[T] = ???

(你说包装的 case class 总是单参数的,参数类型总是 String,参数名称总是 value),你可以尝试使用 Shapeless

import shapeless.{::, Generic, HList, HNil, Typeable}

object caseClassJsonFormats {
  implicit def caseClassJsonFormat[A <: Product, L <: HList](implicit
    gen: Generic.Aux[A, String :: HNil],
    typeable: Typeable[A]
  ): JsonFormat[A] = 
    buildFormatter[A](typeable.describe, gen.to(_).head, s => gen.from(s :: HNil))
}

这样你为所有的 case class 定义了一个通用的隐式(而不是每个 case class 都有一个隐式)。

测试:

final case class ClassName(value: String)

import caseClassJsonFormats._
implicitly[JsonFormat[ClassName]] // 编译通过

另一种方法是使用 宏注解(宏项目的 sbt 设置

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
    
@compileTimeOnly("enable macro annotations")
class jsonFormat extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro JsonFormatMacro.impl
}

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

    def jsonFormatImplicit(tpname: TypeName) =
      q"""implicit val jsonFormatter: _root_.spray.json.JsonFormat[$tpname] =
            buildFormatter[$tpname](${tpname.toString}, _.value, this.apply)"""

    annottees match {
      // if there is companion object, modify it
      case (clazz@q"$_ class $tpname[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ => ..$_ }") ::
        q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        q"""
           $clazz

           $mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
             ..$body

             ${jsonFormatImplicit(tpname)}
           }"""

      // if there is no companion object, create it
      case (clazz@q"$_ class $tpname[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ => ..$_ }") :: Nil =>
        q"""
           $clazz

           object ${tpname.toTermName} {
             ${jsonFormatImplicit(tpname)}
           }"""
    }
  }
}

这样你为每个带注解的 case class 的伴生对象中都定义了一个隐式。

测试:

@jsonFormat
final case class ClassName(value: String)

implicitly[JsonFormat[ClassName]] // 编译通过
英文:

You can google spray json derivation

https://github.com/driver-oss/spray-json-derivation

https://github.com/milessabin/spray-json-shapeless

https://github.com/zackangelo/spray-json-macros

https://github.com/ExNexu/spray-json-annotation

etc.


Since the signature of buildFormatter is

def buildFormatter[T](str: String, value: T =&gt; String, apply: String =&gt; T): JsonFormat[T] = ???

(and you said that wrapper case classes are always single-parameter, parameter type is always String, parameter name is always value) you can try Shapeless

import shapeless.{::, Generic, HList, HNil, Typeable}

object caseClassJsonFormats {
  implicit def caseClassJsonFormat[A &lt;: Product, L &lt;: HList](implicit
    gen: Generic.Aux[A, String :: HNil],
    typeable: Typeable[A]
  ): JsonFormat[A] = 
    buildFormatter[A](typeable.describe, gen.to(_).head, s =&gt; gen.from(s :: HNil))
}

So you define a single implicit for all case classes (instead of an implicit per each case class).

Testing:

final case class ClassName(value: String)

import caseClassJsonFormats._
implicitly[JsonFormat[ClassName]] // compiles

Alternative approach is a macro annotation (sbt settings for macro projects)

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
    
@compileTimeOnly(&quot;enable macro annotations&quot;)
class jsonFormat extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro JsonFormatMacro.impl
}

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

    def jsonFormatImplicit(tpname: TypeName) =
      q&quot;&quot;&quot;implicit val jsonFormatter: _root_.spray.json.JsonFormat[$tpname] =
            buildFormatter[$tpname](${tpname.toString}, _.value, this.apply)&quot;&quot;&quot;

    annottees match {
      // if there is companion object, modify it
      case (clazz@q&quot;$_ class $tpname[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ =&gt; ..$_ }&quot;) ::
        q&quot;$mods object $tname extends { ..$earlydefns } with ..$parents { $self =&gt; ..$body }&quot; :: Nil =&gt;
        q&quot;&quot;&quot;
           $clazz

           $mods object $tname extends { ..$earlydefns } with ..$parents { $self =&gt;
             ..$body

             ${jsonFormatImplicit(tpname)}
           }&quot;&quot;&quot;

      // if there is no companion object, create it
      case (clazz@q&quot;$_ class $tpname[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ =&gt; ..$_ }&quot;) :: Nil =&gt;
        q&quot;&quot;&quot;
           $clazz

           object ${tpname.toTermName} {
             ${jsonFormatImplicit(tpname)}
           }&quot;&quot;&quot;
    }
  }
}

So you define an implicit in companion object per each case class annotated.

Testing:

@jsonFormat
final case class ClassName(value: String)

implicitly[JsonFormat[ClassName]] // compiles

  // scalacOptions += &quot;-Ymacro-debug-lite&quot;
//scalac: {
//  final case class ClassName extends scala.Product with scala.Serializable {
//    &lt;caseaccessor&gt; &lt;paramaccessor&gt; val value: String = _;
//    def &lt;init&gt;(value: String) = {
//      super.&lt;init&gt;();
//      ()
//    }
//  };
//  object ClassName extends scala.AnyRef {
//    def &lt;init&gt;() = {
//      super.&lt;init&gt;();
//      ()
//    };
//    implicit val jsonFormatter: _root_.spray.json.JsonFormat[ClassName] = buildFormatter[ClassName](&quot;ClassName&quot;, ((x$1) =&gt; x$1.value), this.apply)
//  };
//  ()
//}

答案2

得分: 0

可以使用这个选项:

import spray.json.DefaultJsonProtocol._

implicit val format = jsonFormat1(ClassName.apply)

注意:这段代码是Scala语言的代码片段,用于使用Spray JSON库来定义一个类的JSON序列化格式。

英文:

You can use this option:

import spray.json.DefaultJsonProtocol._
implicit val format = jsonFormat1(ClassName.apply)

huangapple
  • 本文由 发表于 2020年10月6日 04:45:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/64215961.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定