基于蛇形命名ADT的Circe ConfiguredJsonCodec替代手动解码器咨询
解答你的Circe JSON API对接问题
1. 用@ConfiguredJsonCodec替代手动Decoder
你的核心需求是让@ConfiguredJsonCodec自动生成Foo的解码器,替代手写的fooDecoder。问题出在默认配置没有处理case object到蛇形字符串的映射,只需要调整Configuration加上构造标签的映射规则即可:
修改后的完整代码
import io.circe._ import io.circe.syntax._ import io.circe.generic.extras.{Configuration, ConfiguredJsonCodec, JsonKeys} import io.circe.parser.parse implicit val jsonConfig: Configuration = Configuration.default .withSnakeCaseConstructorNames .withSnakeCaseMemberNames .withConstructorTagMapper(JsonKeys.snakeCase) // 关键配置:把case object名转成蛇形字符串 @ConfiguredJsonCodec(decodeOnly = true) sealed trait Foo object Foo { case object FooBar extends Foo case object FooBaz extends Foo case object FooWuz extends Foo } @ConfiguredJsonCodec(decodeOnly = true) case class Qux(fooFoo: List[Foo]) val input ="""{"foo_foo" : ["foo_bar", "foo_baz", "foo_wuz"]}""" val json: Json = parse(input).left.map(println(_)).right.get json.as[Qux] // 现在可以正常解码了!
关键说明
withConstructorTagMapper(JsonKeys.snakeCase)会把FooBar这类case object名称转换为foo_bar,完美匹配你的JSON格式- 给
sealed trait Foo加上@ConfiguredJsonCodec(decodeOnly = true)后,Circe会自动生成符合配置的解码器,无需再手动实现fooDecoder Qux的配置保持不变,withSnakeCaseMemberNames已经处理了fooFoo到foo_foo的字段名映射
2. 离散值建模方案讨论
用ADT + case object是非常合理的选择
这是Scala中建模无状态离散枚举值的惯用最佳实践,优势包括:
- 类型安全:编译时就能拦截无效值,比如传入
"invalid_foo"会在解码阶段报错,而如果用原始String类型则无法提前发现问题 - 模式匹配友好:处理
Foo类型时,编译器会检查是否覆盖了所有case object分支,避免遗漏场景 - 轻量高效:case object是单例实例,运行时没有额外内存开销
- 生态兼容:和Circe、Cats等函数式库集成顺畅,通过配置就能轻松处理命名转换、序列化/反序列化
其他可选方案(不推荐或仅适合特定场景)
- Scala原生Enumeration:不推荐,它的类型安全性差(可以用任意Int构造枚举值),模式匹配体验不如ADT,且和Circe的集成需要额外的编码器/解码器实现
- String类型别名:比如
type Foo = String,完全丢失类型安全,无法在编译时验证值的合法性,仅适合快速原型开发 - 带值的case class:如果你的离散值需要携带额外属性(比如显示名称、编码值),可以用
case class FooBar(value: String) extends Foo,但对于单纯的标识性离散值,case object更简洁
内容的提问来源于stack exchange,提问作者Saskia
相关产品推荐
相关产品推荐

