Traversing and modifying JSON

Working with JSON in circe usually involves using a cursor. Cursors are used both for extracting data and for performing modification.

Suppose we have the following JSON document:

import cats.syntax.either._
import io.circe._, io.circe.parser._

val json: String = """
    "id": "c730433b-082c-4984-9d66-855c243266f0",
    "name": "Foo",
    "counts": [1, 2, 3],
    "values": {
      "bar": true,
      "baz": 100.001,
      "qux": ["a", "b"]

val doc: Json = parse(json).getOrElse(Json.Null)

Extracting data

In order to traverse the document we need to create an HCursor with the focus at the document’s root:

val cursor: HCursor = doc.hcursor

We can then use various operations to move the focus of the cursor around the document and extract data from it:

val baz: Decoder.Result[Double] =
// baz: io.circe.Decoder.Result[Double] = Right(100.001)

// You can also use `get[A](key)` as shorthand for `downField(key).as[A]`
val baz2: Decoder.Result[Double] =
// baz2: io.circe.Decoder.Result[Double] = Right(100.001)

val secondQux: Decoder.Result[String] =
// secondQux: io.circe.Decoder.Result[String] = Right(b)

Transforming data

We can also use a cursor to modify JSON.

val reversedNameCursor: ACursor =

We can then return to the root of the document and return its value with top:

val reversedName: Option[Json] =
// reversedName: Option[io.circe.Json] =
// Some({
//   "id" : "c730433b-082c-4984-9d66-855c243266f0",
//   "name" : "ooF",
//   "counts" : [
//     1,
//     2,
//     3
//   ],
//   "values" : {
//     "bar" : true,
//     "baz" : 100.001,
//     "qux" : [
//       "a",
//       "b"
//     ]
//   }
// })

The result contains the original document with the "name" field reversed.

Note that Json is immutable, so the original document is left unchanged.


circe has three slightly different cursor implementations:

  • Cursor provides functionality for moving around a tree and making modifications
  • HCursor tracks the history of operations performed. This can be used to provide useful error messages when something goes wrong.
  • ACursor also tracks history, but represents the possibility of failure (e.g. calling downField on a field that doesn’t exist)


Optics are an alternative way to traverse JSON documents. See the Optics page for more details.