Scala |如何将此代码放入宏注释中?

问题描述

我有很多包装器类,它们所做的就是添加类型安全性和格式化。除了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]

来自评论 问:包装箱类将始终为单参数,参数类型将始终为String,参数名称将始终为value吗? 答:您是正确的,将只有1个参数,参数名称value。我可以限制那些限制

解决方法

您可以搜索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] = ???

(您说包装箱类始终是单参数,参数类型始终为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))
}

因此,您可以为所有案例类定义一个隐式(而不是每个案例类都隐式)。

测试:

final case class ClassName(value: String)

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

替代方法是macro annotation(对于宏项目,是sbt settings

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)}
           }"""
    }
  }
}

因此,您需要为每个带注释的案例类在同伴对象中定义一个隐式对象。

测试:

@jsonFormat
final case class ClassName(value: String)

implicitly[JsonFormat[ClassName]] // compiles

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

您可以使用此选项:

import spray.json.DefaultJsonProtocol._

implicit val format = jsonFormat1(ClassName.apply)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...