scalaintermediate
Advanced JSON with Circe
Handle complex JSON with Circe: custom codecs, optics, cursor navigation, and error handling.
scalaPress ⌘/Ctrl + Shift + C to copy
import io.circe.*
import io.circe.syntax.*
import io.circe.parser.*
import io.circe.generic.auto.*
// Domain models
case class Address(street: String, city: String, zipCode: String)
case class Contact(email: String, phone: Option[String])
case class Person(name: String, age: Int, address: Address, contacts: List[Contact])
// Custom encoder/decoder
sealed trait Status
case object Active extends Status
case object Inactive extends Status
case class Suspended(reason: String) extends Status
given Encoder[Status] = Encoder.instance {
case Active => Json.obj("type" -> "active".asJson)
case Inactive => Json.obj("type" -> "inactive".asJson)
case Suspended(r) => Json.obj("type" -> "suspended".asJson, "reason" -> r.asJson)
}
given Decoder[Status] = Decoder.instance { c =>
c.downField("type").as[String].flatMap {
case "active" => Right(Active)
case "inactive" => Right(Inactive)
case "suspended" => c.downField("reason").as[String].map(Suspended(_))
case other => Left(DecodingFailure(s"Unknown status: $other", c.history))
}
}
// Custom field names
case class ApiResponse(totalCount: Int, pageSize: Int, items: List[String])
given Encoder[ApiResponse] = Encoder.instance { r =>
Json.obj(
"total_count" -> r.totalCount.asJson,
"page_size" -> r.pageSize.asJson,
"items" -> r.items.asJson
)
}
given Decoder[ApiResponse] = Decoder.instance { c =>
for
total <- c.downField("total_count").as[Int]
size <- c.downField("page_size").as[Int]
items <- c.downField("items").as[List[String]]
yield ApiResponse(total, size, items)
}
@main def run(): Unit =
// Encoding
val person = Person(
"Alice", 30,
Address("123 Main St", "Springfield", "62701"),
List(Contact("alice@test.com", Some("555-0123")),
Contact("alice@work.com", None))
)
val json = person.asJson.spaces2
println(s"Encoded:\n$json\n")
// Decoding
val decoded = decode[Person](json)
println(s"Decoded: $decoded\n")
// Cursor navigation
val doc = parse("""
{
"users": [
{"name": "Alice", "scores": [95, 87, 92]},
{"name": "Bob", "scores": [88, 76, 91]}
],
"metadata": {"count": 2, "version": "1.0"}
}
""").getOrElse(Json.Null)
val cursor = doc.hcursor
// Navigate to nested fields
val firstUserName = cursor.downField("users").downN(0).downField("name").as[String]
println(s"First user: $firstUserName")
val allNames = cursor.downField("users").as[List[Json]]
.map(_.flatMap(_.hcursor.downField("name").as[String].toOption))
println(s"All names: $allNames")
val version = cursor.downField("metadata").downField("version").as[String]
println(s"Version: $version")
// Modify JSON
val modified = cursor
.downField("metadata")
.downField("count")
.withFocus(_.mapNumber(n => Json.fromInt(n.toInt.getOrElse(0) + 1).asNumber.get))
.top
println(s"\nModified: ${modified.map(_.spaces2)}")
// Custom status
val statuses: List[Status] = List(Active, Inactive, Suspended("payment"))
println(s"\nStatuses: ${statuses.asJson.spaces2}")
// Error handling
val badJson = """{ "name": 42 }"""
decode[Person](badJson) match
case Left(err) => println(s"\nError: ${err.getMessage}")
case Right(p) => println(s"Parsed: $p")Use Cases
- API response parsing
- Custom JSON serialization
- JSON document transformation
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
scalaintermediate
JSON Parsing with Circe
Parse and generate JSON with Circe: codecs, custom encoders/decoders, and optics.
Best for: REST API JSON handling
#scala#json
scalabeginner
Scala Hello World Application
Create a basic Scala application with main method, string interpolation, and val/var basics.
Best for: Getting started with Scala
#scala#basics
scalabeginner
Pattern Matching Fundamentals
Use Scala pattern matching with guards, type patterns, tuple patterns, and nested extractors.
Best for: Control flow with pattern matching
#scala#pattern-matching
scalabeginner
Case Classes and Objects
Define immutable data with case classes: copy, equality, destructuring, and companion objects.
Best for: Domain modeling with immutable data
#scala#case-class