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)
In order to traverse 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 should be(res0)
You can also use get[A](key)
as shorthand for downField(key).as[A]
.
What would be the result in this case?
val baz2: Decoder.Result[Double] = cursor.downField("values").get[Double]("baz")
baz2 should be(res0)
You can also move to a side of an Array field.
What would the result be when traversing through the array?
val secondQux: Decoder.Result[String] =
cursor.downField("values").downField("qux").downArray.right.as[String]
secondQux should be(res0)
In this section we are going to learn how to use a cursor to modify JSON
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.
Pay attention because we are going to use a .mapString
this time.
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
The result contains the original document, but what would be the result for "name" field?
val nameResult: Result[String] =
cursor.downField("name").withFocus(_.mapString(_.reverse)).as[String]
nameResult should be(res0)