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] =
cursor.downField("values").downField("baz").as[Double]
// baz: Decoder.Result[Double] = Right(value = 100.001)
// You can also use `get[A](key)` as shorthand for `downField(key).as[A]`
val baz2: Decoder.Result[Double] =
cursor.downField("values").get[Double]("baz")
// baz2: Decoder.Result[Double] = Right(value = 100.001)
val secondQux: Decoder.Result[String] =
cursor.downField("values").downField("qux").downArray.as[String]
// secondQux: Decoder.Result[String] = Right(value = "a")
Transforming data
We can also use a cursor to modify JSON.
val reversedNameCursor: ACursor =
cursor.downField("name").withFocus(_.mapString(_.reverse))
We can then return to the root of the document and return its value with top
:
val reversedName: Option[Json] = reversedNameCursor.top
// reversedName: Option[Json] = Some(
// value = JObject(
// value = object[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.
Cursors
circe has three slightly different cursor implementations:
Cursor
provides functionality for moving around a tree and making modificationsHCursor
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. callingdownField
on a field that doesn't exist)
Optics
Optics are an alternative way to traverse JSON documents. See the Optics page for more details.