You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala 3中如何访问未知枚举的valueOf方法并生成Json Codec

Scala 3 枚举Json Codec自动生成的宏错误修复方案

错误原因

你遇到的编译器报错是因为Scala 3对inline方法中宏代码的使用有严格限制:宏代码的splice(${...})必须作为inline方法体的顶层表达式,不能嵌套在lambda、方法调用等内部逻辑里。你之前的代码把${getEnum[T]}放在了mapOrFail的lambda参数中,违反了这个规则。

正确实现方式

把整个Codec的生成逻辑迁移到独立的宏实现方法中,inline方法仅负责调用该宏,确保splice处于顶层位置。

完整代码实现

import scala.util.Try
import scala.quoted.*
import scala.reflect.Enum

// 假设你的Json相关类型定义如下(根据实际库调整)
trait JsonCodec[T] {
  def encoder: JsonEncoder[T]
  def decoder: JsonDecoder[T]
}
object JsonCodec {
  def apply[T](encoder: JsonEncoder[T], decoder: JsonDecoder[T]): JsonCodec[T] = 
    new JsonCodec[T] {
      def encoder = encoder
      def decoder = decoder
    }
}

trait JsonEncoder[T] {
  def encode(value: T): String
  def contramap[U](f: U => T): JsonEncoder[U]
}
object JsonEncoder {
  def apply[T](f: T => String): JsonEncoder[T] = new JsonEncoder[T] {
    def encode(value: T): String = f(value)
    def contramap[U](f: U => T): JsonEncoder[U] = JsonEncoder(f andThen encode)
  }
  given JsonEncoder[String] = JsonEncoder(identity)
}

trait JsonDecoder[T] {
  def decode(value: String): Either[String, T]
  def mapOrFail[U](f: String => Either[String, U]): JsonDecoder[U]
}
object JsonDecoder {
  def apply[T](f: String => Either[String, T]): JsonDecoder[T] = new JsonDecoder[T] {
    def decode(value: String): Either[String, T] = f(value)
    def mapOrFail[U](f: String => Either[String, U]): JsonDecoder[U] = JsonDecoder(f)
  }
  given JsonDecoder[String] = JsonDecoder(Right(_))
}

// 核心生成逻辑
inline def gen[T](using ev: T <:< Enum): JsonCodec[T] = ${ genMacro[T]('ev) }

def genMacro[T: Type](ev: Expr[T <:< Enum])(using Quotes): Expr[JsonCodec[T]] = {
  import quotes.reflect.*
  
  // 获取枚举伴生对象的valueOf方法引用
  val companionRef = Ref(TypeTree.of[T].symbol.companionModule)
  val valueOfExpr = Select.unique(companionRef, "valueOf").asExprOf[String => T]
  
  // 构造Decoder表达式
  val decoderExpr = '{
    JsonDecoder[String].mapOrFail(v => 
      Try($valueOfExpr(v)).fold(
        err => Left(err.getMessage),
        enumVal => Right(enumVal)
      )
    )
  }
  
  // 构造Encoder表达式
  val encoderExpr = '{ JsonEncoder[String].contramap[T](_.toString) }
  
  // 组合生成最终的JsonCodec
  '{ JsonCodec($encoderExpr, $decoderExpr) }
}

关键修改点

  1. 拆分宏逻辑:将原来inline方法内的Encoder/Decoder生成代码移到genMacro宏实现方法中,inline方法仅做顶层splice调用。
  2. 合法引用valueOf:在宏内部通过反射获取枚举伴生对象的valueOf方法,转为类型安全的Expr[String => T],再通过splice($)嵌入到Decoder逻辑中。
  3. 合规的Expr构造:所有运行时代码都通过引号(')和splice组合成Expr,确保宏生成的代码符合编译器要求。

测试示例

定义一个枚举并使用自动生成的Codec:

enum Color extends Enum {
  case Red, Green, Blue
}

// 生成Codec
given colorCodec: JsonCodec[Color] = gen[Color]

// 测试编码解码
val encodedRed = colorCodec.encoder.encode(Color.Red) // 输出 "Red"
val decodedGreen = colorCodec.decoder.decode("Green") // 输出 Right(Green)
val invalidDecode = colorCodec.decoder.decode("Yellow") // 输出 Left("No enum constant Color.Yellow")

内容的提问来源于stack exchange,提问作者sergeda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 02:47:44