scalaintermediate

JSON Parsing with Circe

Parse and generate JSON with Circe: codecs, custom encoders/decoders, and optics.

scala
import io.circe.*
import io.circe.syntax.*
import io.circe.parser.*
import io.circe.generic.auto.*

case class Address(street: String, city: String, zip: String)
case class User(name: String, age: Int, email: String, address: Address)
case class ApiResponse[T](data: T, status: Int, message: String)

@main def run(): Unit =
  // Encoding
  val user = User("Alice", 30, "alice@test.com", Address("123 Main", "NYC", "10001"))
  val json = user.asJson
  println(json.spaces2)

  // Decoding
  val jsonString = """
    |{
    |  "name": "Bob",
    |  "age": 25,
    |  "email": "bob@test.com",
    |  "address": {
    |    "street": "456 Oak Ave",
    |    "city": "LA",
    |    "zip": "90001"
    |  }
    |}
  """.stripMargin

  decode[User](jsonString) match
    case Right(user) => println(s"Parsed: $user")
    case Left(error) => println(s"Error: $error")

  // Manual cursor navigation
  val doc = parse(jsonString).getOrElse(Json.Null)
  val cursor = doc.hcursor

  val name = cursor.downField("name").as[String]
  val city = cursor.downField("address").downField("city").as[String]
  println(s"Name: $name, City: $city")

  // Modify JSON
  val modified = cursor
    .downField("age")
    .withFocus(_.mapNumber(n => Json.fromInt(n.toInt.getOrElse(0) + 1).asNumber.get))
    .top
  println(s"Modified: ${modified.map(_.spaces2)}")

  // Custom encoder/decoder
  case class Money(cents: Long)

  given Encoder[Money] = Encoder.instance { m =>
    Json.obj("amount" -> Json.fromDoubleOrNull(m.cents / 100.0), "currency" -> Json.fromString("USD"))
  }

  given Decoder[Money] = Decoder.instance { c =>
    for
      amount <- c.downField("amount").as[Double]
    yield Money((amount * 100).toLong)
  }

  println(Money(4999).asJson.noSpaces)
  println(decode[Money]("""{"amount":49.99,"currency":"USD"}"""))

  // List encoding
  val users = List(
    User("Alice", 30, "a@t.com", Address("1 St", "NYC", "10001")),
    User("Bob", 25, "b@t.com", Address("2 Ave", "LA", "90001"))
  )
  println(users.asJson.spaces2)

  // API response
  val response = ApiResponse(users, 200, "OK")
  println(response.asJson.spaces2)

Use Cases

  • REST API JSON handling
  • Configuration file parsing
  • Data serialization and deserialization

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.