Scala 3中如何批量生成枚举的重复伴生对象?
如何复用Scala枚举类的伴生对象代码(避免破坏Tapir原生推导)
问题背景
我有多个Scala枚举类,它们的伴生对象代码完全一致,仅枚举类型名称存在差异。之前尝试用trait封装通用的编解码逻辑,但这种方式会破坏Tapir的Codec.deriveEnumeration原生推导机制(该机制要求目标类型必须是密封类或枚举)。
原有重复的伴生对象代码
enum Variants: case A, B, C, D, ... enum Groups: case I, II, III, IV, ... /** Groups及其他枚举的伴生对象代码完全一致 **/ object Variants: import zio.json.* /* ZIO JSON编解码 */ given JsonCodec[Variants] = JsonCodec[Variants]( JsonEncoder[String].contramap[Variants](_.toString), JsonDecoder[String].mapOrFail(name => Try { Variants.valueOf(name) }.toEither.left.map(_.getMessage) ) ) /* Tapir编解码 */ import sttp.tapir.* given Codec[String, Variants, CodecFormat.TextPlain] = Codec.derivedEnumeration[String, Variants].defaultStringBased /* Quill映射编码 */ import io.getquill.MappedEncoding given MappedEncoding[Variants, String](_.toString) given MappedEncoding[String, Variants](Variants.valueOf)
之前尝试的Trait封装方式(存在问题)
这种方式虽然复用了代码,但替换了Tapir原生的derivedEnumeration逻辑,改用parsedString,无法利用Tapir对枚举类型的原生推导能力:
object Variants extends EnumSupport { override type T = Variants } object Groups extends EnumSupport { override type T = Groups } trait EnumSupport: import io.getquill.MappedEncoding import sttp.tapir.* import zio.json.* import scala.util.Try type T <: scala.reflect.Enum & scala.Product def valueOf(n: String): T /* ZIO JSON编解码 */ given JsonCodec[T] = JsonCodec[T]( JsonEncoder[String].contramap[T](_.toString), JsonDecoder[String].mapOrFail(name => Try { this.valueOf(name) }.toEither.left.map(_.getMessage) ) ) /* Tapir编解码(替换了原生deriveEnumeration) */ given Schema[T] = Schema.string[T] given Codec[String, T, CodecFormat.TextPlain] = Codec.parsedString(this.valueOf) /* Quill映射编码 */ given MappedEncoding[T, String](_.toString) given MappedEncoding[String, T](this.valueOf)
解决方案
方案1:Inline工具方法复用逻辑,保留Tapir原生推导
封装通用的inline工具方法,在每个枚举的伴生对象中直接调用,既复用代码又不破坏Tapir的原生推导逻辑:
1. 创建通用枚举编解码工具类
import io.getquill.MappedEncoding import sttp.tapir.* import zio.json.* import scala.util.Try object EnumCodecs: // 通用ZIO JSON编解码生成方法 inline def zioJsonCodec[T <: scala.reflect.Enum & scala.Product](inline valueOf: String => T): JsonCodec[T] = JsonCodec[T]( JsonEncoder[String].contramap[T](_.toString), JsonDecoder[String].mapOrFail(name => Try(valueOf(name)).toEither.left.map(_.getMessage) ) ) // 通用Tapir Codec生成方法(保留原生deriveEnumeration逻辑) inline def tapirCodec[T <: scala.reflect.Enum & scala.Product]: Codec[String, T, CodecFormat.TextPlain] = Codec.derivedEnumeration[String, T].defaultStringBased // 通用Quill映射编码生成方法 inline def quillEncodings[T <: scala.reflect.Enum & scala.Product](inline valueOf: String => T): (MappedEncoding[T, String], MappedEncoding[String, T]) = (MappedEncoding[T, String](_.toString), MappedEncoding[String, T](valueOf))
2. 在枚举伴生对象中引入通用方法
enum Variants: case A, B, C, D object Variants: import EnumCodecs.* // ZIO JSON given JsonCodec[Variants] = zioJsonCodec(Variants.valueOf) // Tapir(保留原生deriveEnumeration) given Codec[String, Variants, CodecFormat.TextPlain] = tapirCodec[Variants] // Quill val (encodeVariants, decodeVariants) = quillEncodings(Variants.valueOf) given MappedEncoding[Variants, String] = encodeVariants given MappedEncoding[String, Variants] = decodeVariants enum Groups: case I, II, III, IV object Groups: import EnumCodecs.* given JsonCodec[Groups] = zioJsonCodec(Groups.valueOf) given Codec[String, Groups, CodecFormat.TextPlain] = tapirCodec[Groups] val (encodeGroups, decodeGroups) = quillEncodings(Groups.valueOf) given MappedEncoding[Groups, String] = encodeGroups given MappedEncoding[String, Groups] = decodeGroups
方案2:特质混合+Inline类型投影
通过inline关键字确保Tapir能正确识别枚举类型,进一步减少重复代码:
1. 调整后的EnumSupport特质
trait EnumSupport[T <: scala.reflect.Enum & scala.Product]: import io.getquill.MappedEncoding import sttp.tapir.* import zio.json.* import scala.util.Try def valueOf(n: String): T // ZIO JSON given JsonCodec[T] = JsonCodec[T]( JsonEncoder[String].contramap[T](_.toString), JsonDecoder[String].mapOrFail(name => Try(valueOf(name)).toEither.left.map(_.getMessage) ) ) // Tapir:使用inline保留原生deriveEnumeration逻辑 inline given Codec[String, T, CodecFormat.TextPlain] = Codec.derivedEnumeration[String, T].defaultStringBased // Quill given MappedEncoding[T, String](_.toString) given MappedEncoding[String, T](valueOf)
2. 枚举伴生对象继承特质
enum Variants: case A, B, C, D object Variants extends EnumSupport[Variants]: override def valueOf(n: String): Variants = Variants.valueOf(n) enum Groups: case I, II, III, IV object Groups extends EnumSupport[Groups]: override def valueOf(n: String): Groups = Groups.valueOf(n)
关键说明
- 方案1更灵活,每个枚举可按需选择引入的编解码逻辑,完全保留Tapir原生推导能力。
- 方案2进一步精简代码,通过inline确保Tapir能正确解析枚举类型元数据,避免了之前替换
derivedEnumeration的问题。
内容的提问来源于stack exchange,提问作者andreoss
相关产品推荐
相关产品推荐

