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

使用play-json-derived-codecs无法序列化键为密封特质的Map问题

问题成因

Play JSON 对 Map[K, V] 类型的序列化逻辑有特殊要求:JSON 规范中对象的键只能是字符串类型,因此 Play JSON 默认只为键类型存在 KeyFormat 隐式实例的 Map 提供 Format 实现。
你使用 play-json-derived-codecs 的 derived.oformat[Baz]() 生成的是 OFormat[Baz] 实例,该实例仅支持将 Baz 类型作为JSON值(比如普通的对象、字符串等字段值)序列化,不满足 Map 键需要的 KeyFormat 约束。当你把键类型改为 String 时运行正常,是因为 Play JSON 已经默认提供了 String 类型的 KeyFormat 隐式实现。

解决方案

有两种常见的修复方式:

方案1:为Baz实现自定义KeyFormat

在 Baz 的伴生对象中新增 KeyFormat[Baz] 隐式实例,定义 Baz 和字符串之间的互转逻辑即可:

sealed trait Baz
object Baz {
  case object A extends Baz

  implicit val format: OFormat[Baz] = derived.oformat[Baz]()
  // 新增KeyFormat实现
  implicit val bazKeyFormat: KeyFormat[Baz] = new KeyFormat[Baz] {
    override def read(key: String): JsResult[Baz] = key match {
      case "A" => JsSuccess(Baz.A)
      case other => JsError(s"非法的Baz键值: $other")
    }
    override def write(baz: Baz): String = baz match {
      case Baz.A => "A"
    }
  }
}
final case class Foo(s: Map[Baz, String])
object Foo {
  implicit val format: OFormat[Foo] = Json.format[Foo]
}

方案2:调整存储结构规避Map键约束

如果不想额外实现 KeyFormat,可以将 Foo 中的 Map 类型替换为 Seq[(Baz, String)],用键值对数组的形式存储数据,此时 Baz 作为JSON值序列化,仅需要已有的 OFormat 实例即可正常工作:

final case class Foo(s: Seq[(Baz, String)]) {
  // 业务需要使用Map时可通过该方法转换
  def sAsMap: Map[Baz, String] = s.toMap
}
object Foo {
  implicit val format: OFormat[Foo] = Json.format[Foo]
}

内容的提问来源于stack exchange,提问作者Mojo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 08:36:05