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

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为例:

  1. 添加circe-generic-extras依赖;
  2. 配置派生规则并结合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:50:01