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) } }
关键修改点
- 拆分宏逻辑:将原来inline方法内的Encoder/Decoder生成代码移到
genMacro宏实现方法中,inline方法仅做顶层splice调用。 - 合法引用valueOf:在宏内部通过反射获取枚举伴生对象的
valueOf方法,转为类型安全的Expr[String => T],再通过splice($)嵌入到Decoder逻辑中。 - 合规的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
相关产品推荐
相关产品推荐

