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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 15:14:51