自定义编解码器
处理复杂的数据形状。
自定义编解码器 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
何时需要自定义编解码器
派生可以覆盖常见情况,但有时 JSON 的结构并不反映您的样例类,例如字段名称来自旧系统、枚举经过编码,或者使用时间戳等格式。
这时,您需要手动编写 Decoder、Encoder,或同时编写两者。
从游标创建解码器
构建解码器最明确的方式是使用 Decoder.instance 接收一个 HCursor。
您可以使用 downField 和 as 导航字段,并返回一个 Either。
import io.circe.Decoder
case class User(name: String, age: Int)
implicit val dec: Decoder[User] = Decoder.instance { c =>
for {
n <- c.downField("full_name").as[String]
a <- c.downField("years").as[Int]
} yield User(n, a)
}forProductN 辅助工具
对于直接的映射,Decoder.forProduct2 更加简洁:列出 JSON 键,然后传入构造函数。
从 forProduct1 开始,一直到支持许多参数,都有相应的辅助工具。
import io.circe.Decoder
implicit val dec: Decoder[User] =
Decoder.forProduct2("full_name", "years")(User.apply)手动编写编码器
使用 Encoder.instance 与解码器对应起来,根据您的字段构建一个 Json 对象。
或者使用 Encoder.forProduct2,通过一次调用将字段映射回指定的 JSON 键。
import io.circe.Encoder
implicit val enc: Encoder[User] =
Encoder.forProduct2("full_name", "years")(u =>
(u.name, u.age))转换现有编解码器
您很少需要从零开始。map 可以调整解码器的输出,contramap 可以调整编码器的输入。
emap 类似于 map,但可能失败;它会返回 Either[String, A] 以执行验证。
import io.circe.Decoder
case class Age(value: Int)
implicit val dec: Decoder[Age] =
Decoder[Int].emap { i =>
if (i >= 0) Right(Age(i)) else Left("negative age")
}枚举的编解码器
带有样例对象的密封特征可以用来表示枚举。将每个枚举值编码为标签字符串,然后通过匹配该字符串进行解码。
emap 会将未知标签转换为清晰的解码失败结果。
sealed trait Role
case object Admin extends Role
case object Guest extends Role
implicit val dec: Decoder[Role] = Decoder[String].emap {
case "admin" => Right(Admin)
case "guest" => Right(Guest)
case other => Left(s"unknown role: $other")
}将枚举重新编码
将枚举解码器与使用 contramap 创建的编码器配对,让编码器将每个枚举值转换为相应标签。
现在,这个密封特征在 JSON 中可以作为普通字符串完成往返转换。
import io.circe.Encoder
implicit val enc: Encoder[Role] = Encoder[String].contramap {
case Admin => "admin"
case Guest => "guest"
}日期的自定义编解码器
Circe 核心模块没有内置的 java.time 编解码器,因此日期是使用自定义编解码器的典型场景。
通过 ISO 字符串格式进行解码和编码。
import java.time.LocalDate
import io.circe.{Decoder, Encoder}
implicit val dec: Decoder[LocalDate] =
Decoder[String].map(LocalDate.parse)
implicit val enc: Encoder[LocalDate] =
Encoder[String].contramap(_.toString)打包为 Codec
当您同时拥有两个方向的转换时,可以使用 Codec.from(decoder, encoder) 将它们组合为一个 Codec[A]。
这样只需在作用域中保留一个隐式值,而不是两个彼此独立的隐式值。
import io.circe.Codec
implicit val roleCodec: Codec[Role] =
Codec.from(dec, enc)ADT 的判别字段
对于包含数据的密封层次结构,circe-generic-extras 会添加判别字段,而不是使用包装对象。
只需配置一次,即可为整个 ADT 派生编解码器。
import io.circe.generic.extras._
implicit val cfg: Configuration =
Configuration.default.withDiscriminator("type")
@ConfiguredJsonCodec sealed trait Shape
@ConfiguredJsonCodec case class Circle(r: Double) extends Shape隐式作用域与优先级
只要手写的隐式编解码器处于作用域内,它的优先级就高于派生结果。请将它放入伴生对象中,以便始终能够找到它。
对于同一类型,请避免同时导入 auto._ 和手动实例,以免产生歧义。
快速检查
检验您对自定义编解码器的理解。
回顾
自定义编解码器可以处理与您的类型不匹配的 JSON。您可以使用 Decoder.instance、forProductN 构建它们,也可以通过 map、contramap 和 emap 转换现有编解码器。
枚举和日期是常见场景;使用 Codec.from 将两个方向的转换打包在一起,并使用判别字段处理 ADT。
常见问题解答
「自定义编解码器」课时是免费的吗?
是的 — 「自定义编解码器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
「自定义编解码器」这节课中我会学到什么?
处理复杂的数据形状。 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「自定义编解码器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?
能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。