Scala3中值类(Value Class)如何使用derives推导JsonCodec?
问题解答
为什么带derives的代码无法编译?
Scala3的derives关键字依赖类型类(这里是JsonCodec)的派生器实例自动生成类型类实现,但值类(继承AnyVal的单字段case class)是Scala的特殊优化类型:
- 值类在运行时会被编译器优化为底层原始类型(比如
ItemId会直接以Long形式存在),而非普通的case class实例; - 主流JSON库的默认
JsonCodec派生器是为普通多字段case class设计的,它们会解析case class的结构元数据,但值类的单字段包装结构无法被默认派生逻辑正确识别,因此无法生成有效的JsonCodec[ItemId]实例。
Scala3中值类正确使用derives的方法
方法一:手动实现(最通用可靠)
就是你目前使用的临时方案,利用底层类型的JsonCodec通过transform转换,这种方式不依赖库的特殊支持,逻辑清晰:
final case class ItemId(value: Long) extends AnyVal object ItemId: given codec: JsonCodec[ItemId] = JsonCodec[Long].transform(ItemId(_), _.value)
方法二:利用JSON库的值类派生扩展
如果使用的JSON库提供了值类的派生支持,可以启用对应功能来使用derives。以Circe为例:
- 添加
circe-generic-extras依赖; - 配置派生规则并结合
derives使用:
import io.circe.Codec import io.circe.generic.extras.Configuration import io.circe.generic.extras.semiauto.deriveConfiguredCodec final case class ItemId(value: Long) extends AnyVal derives Codec object ItemId: given Configuration = Configuration.default.withUnwrapValueClasses(true)
部分库还会提供专门的派生器类型,允许直接通过derives声明,比如:
final case class ItemId(value: Long) extends AnyVal derives JsonCodec.ValueClassDeriver
具体写法需要参考你使用的JSON库的文档。
方法三:自定义通用值类派生器
如果需要在多个值类中复用逻辑,可以编写一个通用的派生器,让derives能自动处理值类:
trait JsonCodec[T]: def encode(t: T): String def decode(s: String): T object JsonCodec: // 为单字段值类提供通用派生逻辑 inline given deriveValueClass[V <: AnyVal, U](using inline ev: V <:< Product, codec: JsonCodec[U], inline create: U => V, inline extract: V => U ): JsonCodec[V] = codec.transform(create, extract) // 使用时 final case class ItemId(value: Long) extends AnyVal derives JsonCodec
这种方式需要利用Scala3的内联和类型匹配能力,确保派生器能识别值类的包装关系。
内容的提问来源于stack exchange,提问作者mkUltra
相关产品推荐
相关产品推荐

