Custom Codecs
Handle tricky shapes.
When You Need Custom Codecs
Derivation covers the common case, but sometimes JSON does not mirror your case class: legacy field names, encoded enums, or formats like timestamps.
Then you write a Decoder, an Encoder, or both by hand.
A Decoder from a Cursor
The most explicit way to build a decoder is Decoder.instance, receiving an HCursor.
You navigate fields with downField and as, returning an 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)
}