CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-circe--circe-core

Core module of circe, a JSON library for Scala that enables developers to encode and decode JSON data with type safety and functional programming principles.

Pending
Overview
Eval results
Files

cursor-navigation.mddocs/

Cursor Navigation

Circe provides a zipper-like cursor API for navigating and manipulating JSON structures. Cursors allow safe traversal of JSON documents with comprehensive error tracking.

ACursor

Abstract base class for all cursor types.

abstract class ACursor {
  // Current state
  def focus: Option[Json]
  def succeeded: Boolean
  def failed: Boolean
  def success: Option[HCursor]
  
  // History and path
  def history: List[CursorOp]
  def pathString: String
  def pathToRoot: PathToRoot
  
  // Navigation
  def up: ACursor
  def left: ACursor
  def right: ACursor
  def downArray: ACursor
  def downN(n: Int): ACursor
  def downField(k: String): ACursor
  def downFields(k1: String, k2: String, ks: String*): ACursor
  def field(k: String): ACursor
  def find(p: Json => Boolean): ACursor
  def leftN(n: Int): ACursor
  def rightN(n: Int): ACursor
  def leftAt(p: Json => Boolean): ACursor
  def rightAt(p: Json => Boolean): ACursor
  
  // Array access
  def values: Option[Iterable[Json]]
  def index: Option[Int]
  
  // Object access
  def keys: Option[Iterable[String]]
  def key: Option[String]
  def field(k: String): Option[Json]
  
  // Transformation
  def withFocus(f: Json => Json): ACursor
  def withFocusM[F[_]](f: Json => F[Json])(implicit F: Applicative[F]): F[ACursor]
  def set(j: Json): ACursor
  def delete: ACursor
  
  // Decoding
  def as[A](implicit d: Decoder[A]): Decoder.Result[A]
  def get[A](k: String)(implicit d: Decoder[A]): Decoder.Result[A]
  def getOrElse[A](k: String)(default: => A)(implicit d: Decoder[A]): A
  
  // Replay operations
  def replayOne(op: CursorOp): ACursor
  def replay(history: List[CursorOp]): ACursor
  
  // Root navigation
  def top: Option[Json]
  def root: HCursor
}

HCursor

Successful cursor with a guaranteed focus.

abstract class HCursor extends ACursor {
  // Guaranteed focus
  def value: Json
  
  // Always successful
  override def succeeded: Boolean = true
  override def focus: Option[Json] = Some(value)
  override def success: Option[HCursor] = Some(this)
  
  // Abstract methods for implementations
  def replace(newValue: Json, cursor: HCursor, op: CursorOp): HCursor
  def addOp(cursor: HCursor, op: CursorOp): HCursor
}

HCursor Companion Object

object HCursor {
  def fromJson(value: Json): HCursor
}

FailedCursor

Cursor representing failed navigation.

final class FailedCursor extends ACursor {
  // Failure information
  def incorrectFocus: Boolean
  def missingField: Boolean
  
  // Always failed
  override def succeeded: Boolean = false
  override def failed: Boolean = true
  override def focus: Option[Json] = None
  override def success: Option[HCursor] = None
  
  // All navigation operations return this (no-ops)
  override def up: ACursor = this
  override def left: ACursor = this
  override def right: ACursor = this
  // ... etc for all navigation methods
}

CursorOp

Represents cursor navigation operations for error tracking.

sealed abstract class CursorOp

// Basic navigation
case object MoveLeft extends CursorOp
case object MoveRight extends CursorOp  
case object MoveUp extends CursorOp

// Field operations
final case class Field(k: String) extends CursorOp
final case class DownField(k: String) extends CursorOp

// Array operations
case object DownArray extends CursorOp
final case class DownN(n: Int) extends CursorOp

// Delete operation
case object DeleteGoParent extends CursorOp

CursorOp Companion Object

object CursorOp {
  // Convert operations to path string
  def opsToPath(history: List[CursorOp]): String
  
  // Operation categories
  abstract class ObjectOp extends CursorOp
  abstract class ArrayOp extends CursorOp  
  abstract class UnconstrainedOp extends CursorOp
}

PathToRoot

Represents the path from cursor position to document root.

final case class PathToRoot(elems: List[PathElem]) {
  def asPathString: String
}

sealed abstract class PathElem
final case class ObjectKey(key: String) extends PathElem
final case class ArrayIndex(index: Long) extends PathElem

PathToRoot Companion Object

object PathToRoot {
  val empty: PathToRoot
  def fromHistory(history: List[CursorOp]): Either[String, PathToRoot]
  def toPathString(pathToRoot: PathToRoot): String
}

Usage Examples

Basic Navigation

import io.circe._
import io.circe.parser._

val jsonString = """
{
  "users": [
    {"name": "John", "age": 30, "address": {"city": "NYC"}},
    {"name": "Jane", "age": 25, "address": {"city": "LA"}}
  ],
  "total": 2
}
"""

val json = parse(jsonString).getOrElse(Json.Null)
val cursor = json.hcursor

// Navigate to first user's name
val firstName = cursor
  .downField("users")
  .downArray
  .downField("name")
  .as[String]
// Result: Right("John")

// Navigate to second user's city
val secondCity = cursor
  .downField("users")
  .downN(1)
  .downField("address")
  .downField("city")
  .as[String]
// Result: Right("LA")

// Get total count
val total = cursor.downField("total").as[Int]
// Result: Right(2)

Error Handling with Failed Cursors

import io.circe._
import io.circe.parser._

val json = parse("""{"name": "John", "age": 30}""").getOrElse(Json.Null)
val cursor = json.hcursor

// Successful navigation
val name = cursor.downField("name").as[String]
// Result: Right("John")

// Failed navigation - missing field
val emailCursor = cursor.downField("email")
println(emailCursor.succeeded) // false
println(emailCursor.failed)    // true

val email = emailCursor.as[String]
// Result: Left(DecodingFailure(Missing required field, List(DownField(email))))

// Failed navigation - wrong type
val ageCursor = cursor.downField("age").downArray
println(ageCursor.succeeded) // false

// Check failure reasons for FailedCursor
emailCursor match {
  case failed: FailedCursor =>
    println(failed.missingField)   // true
    println(failed.incorrectFocus) // false
  case _ => // ...
}

Cursor Manipulation

import io.circe._
import io.circe.parser._

val json = parse("""{"name": "John", "age": 30}""").getOrElse(Json.Null)
val cursor = json.hcursor

// Modify a field
val modifiedCursor = cursor
  .downField("name")
  .withFocus(_.asString.map(name => Json.fromString(name.toUpperCase)).getOrElse(Json.Null))

val newJson = modifiedCursor.top.getOrElse(Json.Null)
// Result: {"name": "JOHN", "age": 30}

// Set a field value
val updatedCursor = cursor
  .downField("age")
  .set(Json.fromInt(31))

val updatedJson = updatedCursor.top.getOrElse(Json.Null)
// Result: {"name": "John", "age": 31}

// Delete a field
val deletedCursor = cursor
  .downField("age")
  .delete

val deletedJson = deletedCursor.top.getOrElse(Json.Null)
// Result: {"name": "John"}

Array Navigation

import io.circe._
import io.circe.parser._

val json = parse("""{"items": [1, 2, 3, 4, 5]}""").getOrElse(Json.Null)
val cursor = json.hcursor

// Navigate to array
val arraysCursor = cursor.downField("items").downArray

// Get first element
val first = arraysCursor.as[Int]
// Result: Right(1)

// Navigate to specific indices
val third = cursor.downField("items").downN(2).as[Int]
// Result: Right(3)

// Navigate between array elements
val second = arraysCursor.right.as[Int]
// Result: Right(2)

val fourth = arraysCursor.right.right.right.as[Int]
// Result: Right(4)

// Access array information
val itemsCursor = cursor.downField("items")
val values = itemsCursor.values
// Result: Some(Vector(1, 2, 3, 4, 5))

Complex Navigation

import io.circe._
import io.circe.parser._

val json = parse("""
{
  "company": {
    "departments": [
      {
        "name": "Engineering",
        "employees": [
          {"name": "Alice", "role": "Engineer"},
          {"name": "Bob", "role": "Senior Engineer"}
        ]
      },
      {
        "name": "Sales", 
        "employees": [
          {"name": "Charlie", "role": "Sales Rep"}
        ]
      }
    ]
  }
}
""").getOrElse(Json.Null)

val cursor = json.hcursor

// Navigate to first department name
val firstDeptName = cursor
  .downField("company")
  .downField("departments")
  .downArray
  .downField("name")
  .as[String]
// Result: Right("Engineering")

// Navigate to Alice's role
val aliceRole = cursor
  .downField("company")
  .downField("departments")
  .downN(0)
  .downField("employees")
  .downN(0)
  .downField("role")
  .as[String]
// Result: Right("Engineer")

// Use downFields for multiple field navigation
val charlieRole = cursor
  .downFields("company", "departments")
  .downN(1)
  .downFields("employees")
  .downArray
  .downField("role")
  .as[String]
// Result: Right("Sales Rep")

Finding Elements

import io.circe._
import io.circe.parser._

val json = parse("""
{
  "users": [
    {"name": "John", "active": true},
    {"name": "Jane", "active": false},
    {"name": "Bob", "active": true}
  ]
}
""").getOrElse(Json.Null)

val cursor = json.hcursor

// Find first active user
val activeUserCursor = cursor
  .downField("users")
  .downArray
  .find(_.hcursor.downField("active").as[Boolean].contains(true))

val activeUserName = activeUserCursor.downField("name").as[String]
// Result: Right("John")

// Find by name
val janeCursor = cursor
  .downField("users")
  .downArray
  .find(_.hcursor.downField("name").as[String].contains("Jane"))

val janeActive = janeCursor.downField("active").as[Boolean]
// Result: Right(false)

Path Tracking

import io.circe._
import io.circe.parser._

val json = parse("""{"user": {"profile": {"name": "John"}}}""").getOrElse(Json.Null)
val cursor = json.hcursor

val nameCursor = cursor
  .downField("user")
  .downField("profile")
  .downField("name")

// Get path information
println(nameCursor.pathString)
// Result: ".user.profile.name"

// Get operation history
println(nameCursor.history)
// Result: List(DownField(user), DownField(profile), DownField(name))

// Failed navigation path tracking
val failedCursor = cursor.downField("nonexistent").downField("field")
println(failedCursor.pathString)
// Result: ".nonexistent.field"

Cursor Utilities

import io.circe._

val cursor: HCursor = Json.obj("name" -> Json.fromString("John")).hcursor

// Helper methods for safe access
def getName(c: HCursor): String = 
  c.get[String]("name").getOrElse("Unknown")

def getAge(c: HCursor): Int = 
  c.getOrElse[Int]("age")(0)

// Replay operations on different JSON
val operations = List(CursorOp.DownField("user"), CursorOp.DownField("name"))
val newJson = Json.obj("user" -> Json.obj("name" -> Json.fromString("Jane")))
val replayedCursor = newJson.hcursor.replay(operations)
// Result: cursor positioned at "Jane"

Install with Tessl CLI

npx tessl i tessl/maven-io-circe--circe-core

docs

cursor-navigation.md

error-handling.md

index.md

json-data-types.md

json-printing.md

key-encoding-decoding.md

type-classes.md

tile.json