or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cursor-navigation.mderror-handling.mdindex.mdjson-data-types.mdjson-printing.mdkey-encoding-decoding.mdtype-classes.md

cursor-navigation.mddocs/

0

# Cursor Navigation

1

2

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

3

4

## ACursor

5

6

Abstract base class for all cursor types.

7

8

```scala { .api }

9

abstract class ACursor {

10

// Current state

11

def focus: Option[Json]

12

def succeeded: Boolean

13

def failed: Boolean

14

def success: Option[HCursor]

15

16

// History and path

17

def history: List[CursorOp]

18

def pathString: String

19

def pathToRoot: PathToRoot

20

21

// Navigation

22

def up: ACursor

23

def left: ACursor

24

def right: ACursor

25

def downArray: ACursor

26

def downN(n: Int): ACursor

27

def downField(k: String): ACursor

28

def downFields(k1: String, k2: String, ks: String*): ACursor

29

def field(k: String): ACursor

30

def find(p: Json => Boolean): ACursor

31

def leftN(n: Int): ACursor

32

def rightN(n: Int): ACursor

33

def leftAt(p: Json => Boolean): ACursor

34

def rightAt(p: Json => Boolean): ACursor

35

36

// Array access

37

def values: Option[Iterable[Json]]

38

def index: Option[Int]

39

40

// Object access

41

def keys: Option[Iterable[String]]

42

def key: Option[String]

43

def field(k: String): Option[Json]

44

45

// Transformation

46

def withFocus(f: Json => Json): ACursor

47

def withFocusM[F[_]](f: Json => F[Json])(implicit F: Applicative[F]): F[ACursor]

48

def set(j: Json): ACursor

49

def delete: ACursor

50

51

// Decoding

52

def as[A](implicit d: Decoder[A]): Decoder.Result[A]

53

def get[A](k: String)(implicit d: Decoder[A]): Decoder.Result[A]

54

def getOrElse[A](k: String)(default: => A)(implicit d: Decoder[A]): A

55

56

// Replay operations

57

def replayOne(op: CursorOp): ACursor

58

def replay(history: List[CursorOp]): ACursor

59

60

// Root navigation

61

def top: Option[Json]

62

def root: HCursor

63

}

64

```

65

66

## HCursor

67

68

Successful cursor with a guaranteed focus.

69

70

```scala { .api }

71

abstract class HCursor extends ACursor {

72

// Guaranteed focus

73

def value: Json

74

75

// Always successful

76

override def succeeded: Boolean = true

77

override def focus: Option[Json] = Some(value)

78

override def success: Option[HCursor] = Some(this)

79

80

// Abstract methods for implementations

81

def replace(newValue: Json, cursor: HCursor, op: CursorOp): HCursor

82

def addOp(cursor: HCursor, op: CursorOp): HCursor

83

}

84

```

85

86

### HCursor Companion Object

87

88

```scala { .api }

89

object HCursor {

90

def fromJson(value: Json): HCursor

91

}

92

```

93

94

## FailedCursor

95

96

Cursor representing failed navigation.

97

98

```scala { .api }

99

final class FailedCursor extends ACursor {

100

// Failure information

101

def incorrectFocus: Boolean

102

def missingField: Boolean

103

104

// Always failed

105

override def succeeded: Boolean = false

106

override def failed: Boolean = true

107

override def focus: Option[Json] = None

108

override def success: Option[HCursor] = None

109

110

// All navigation operations return this (no-ops)

111

override def up: ACursor = this

112

override def left: ACursor = this

113

override def right: ACursor = this

114

// ... etc for all navigation methods

115

}

116

```

117

118

## CursorOp

119

120

Represents cursor navigation operations for error tracking.

121

122

```scala { .api }

123

sealed abstract class CursorOp

124

125

// Basic navigation

126

case object MoveLeft extends CursorOp

127

case object MoveRight extends CursorOp

128

case object MoveUp extends CursorOp

129

130

// Field operations

131

final case class Field(k: String) extends CursorOp

132

final case class DownField(k: String) extends CursorOp

133

134

// Array operations

135

case object DownArray extends CursorOp

136

final case class DownN(n: Int) extends CursorOp

137

138

// Delete operation

139

case object DeleteGoParent extends CursorOp

140

```

141

142

### CursorOp Companion Object

143

144

```scala { .api }

145

object CursorOp {

146

// Convert operations to path string

147

def opsToPath(history: List[CursorOp]): String

148

149

// Operation categories

150

abstract class ObjectOp extends CursorOp

151

abstract class ArrayOp extends CursorOp

152

abstract class UnconstrainedOp extends CursorOp

153

}

154

```

155

156

## PathToRoot

157

158

Represents the path from cursor position to document root.

159

160

```scala { .api }

161

final case class PathToRoot(elems: List[PathElem]) {

162

def asPathString: String

163

}

164

165

sealed abstract class PathElem

166

final case class ObjectKey(key: String) extends PathElem

167

final case class ArrayIndex(index: Long) extends PathElem

168

```

169

170

### PathToRoot Companion Object

171

172

```scala { .api }

173

object PathToRoot {

174

val empty: PathToRoot

175

def fromHistory(history: List[CursorOp]): Either[String, PathToRoot]

176

def toPathString(pathToRoot: PathToRoot): String

177

}

178

```

179

180

## Usage Examples

181

182

### Basic Navigation

183

184

```scala

185

import io.circe._

186

import io.circe.parser._

187

188

val jsonString = """

189

{

190

"users": [

191

{"name": "John", "age": 30, "address": {"city": "NYC"}},

192

{"name": "Jane", "age": 25, "address": {"city": "LA"}}

193

],

194

"total": 2

195

}

196

"""

197

198

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

199

val cursor = json.hcursor

200

201

// Navigate to first user's name

202

val firstName = cursor

203

.downField("users")

204

.downArray

205

.downField("name")

206

.as[String]

207

// Result: Right("John")

208

209

// Navigate to second user's city

210

val secondCity = cursor

211

.downField("users")

212

.downN(1)

213

.downField("address")

214

.downField("city")

215

.as[String]

216

// Result: Right("LA")

217

218

// Get total count

219

val total = cursor.downField("total").as[Int]

220

// Result: Right(2)

221

```

222

223

### Error Handling with Failed Cursors

224

225

```scala

226

import io.circe._

227

import io.circe.parser._

228

229

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

230

val cursor = json.hcursor

231

232

// Successful navigation

233

val name = cursor.downField("name").as[String]

234

// Result: Right("John")

235

236

// Failed navigation - missing field

237

val emailCursor = cursor.downField("email")

238

println(emailCursor.succeeded) // false

239

println(emailCursor.failed) // true

240

241

val email = emailCursor.as[String]

242

// Result: Left(DecodingFailure(Missing required field, List(DownField(email))))

243

244

// Failed navigation - wrong type

245

val ageCursor = cursor.downField("age").downArray

246

println(ageCursor.succeeded) // false

247

248

// Check failure reasons for FailedCursor

249

emailCursor match {

250

case failed: FailedCursor =>

251

println(failed.missingField) // true

252

println(failed.incorrectFocus) // false

253

case _ => // ...

254

}

255

```

256

257

### Cursor Manipulation

258

259

```scala

260

import io.circe._

261

import io.circe.parser._

262

263

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

264

val cursor = json.hcursor

265

266

// Modify a field

267

val modifiedCursor = cursor

268

.downField("name")

269

.withFocus(_.asString.map(name => Json.fromString(name.toUpperCase)).getOrElse(Json.Null))

270

271

val newJson = modifiedCursor.top.getOrElse(Json.Null)

272

// Result: {"name": "JOHN", "age": 30}

273

274

// Set a field value

275

val updatedCursor = cursor

276

.downField("age")

277

.set(Json.fromInt(31))

278

279

val updatedJson = updatedCursor.top.getOrElse(Json.Null)

280

// Result: {"name": "John", "age": 31}

281

282

// Delete a field

283

val deletedCursor = cursor

284

.downField("age")

285

.delete

286

287

val deletedJson = deletedCursor.top.getOrElse(Json.Null)

288

// Result: {"name": "John"}

289

```

290

291

### Array Navigation

292

293

```scala

294

import io.circe._

295

import io.circe.parser._

296

297

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

298

val cursor = json.hcursor

299

300

// Navigate to array

301

val arraysCursor = cursor.downField("items").downArray

302

303

// Get first element

304

val first = arraysCursor.as[Int]

305

// Result: Right(1)

306

307

// Navigate to specific indices

308

val third = cursor.downField("items").downN(2).as[Int]

309

// Result: Right(3)

310

311

// Navigate between array elements

312

val second = arraysCursor.right.as[Int]

313

// Result: Right(2)

314

315

val fourth = arraysCursor.right.right.right.as[Int]

316

// Result: Right(4)

317

318

// Access array information

319

val itemsCursor = cursor.downField("items")

320

val values = itemsCursor.values

321

// Result: Some(Vector(1, 2, 3, 4, 5))

322

```

323

324

### Complex Navigation

325

326

```scala

327

import io.circe._

328

import io.circe.parser._

329

330

val json = parse("""

331

{

332

"company": {

333

"departments": [

334

{

335

"name": "Engineering",

336

"employees": [

337

{"name": "Alice", "role": "Engineer"},

338

{"name": "Bob", "role": "Senior Engineer"}

339

]

340

},

341

{

342

"name": "Sales",

343

"employees": [

344

{"name": "Charlie", "role": "Sales Rep"}

345

]

346

}

347

]

348

}

349

}

350

""").getOrElse(Json.Null)

351

352

val cursor = json.hcursor

353

354

// Navigate to first department name

355

val firstDeptName = cursor

356

.downField("company")

357

.downField("departments")

358

.downArray

359

.downField("name")

360

.as[String]

361

// Result: Right("Engineering")

362

363

// Navigate to Alice's role

364

val aliceRole = cursor

365

.downField("company")

366

.downField("departments")

367

.downN(0)

368

.downField("employees")

369

.downN(0)

370

.downField("role")

371

.as[String]

372

// Result: Right("Engineer")

373

374

// Use downFields for multiple field navigation

375

val charlieRole = cursor

376

.downFields("company", "departments")

377

.downN(1)

378

.downFields("employees")

379

.downArray

380

.downField("role")

381

.as[String]

382

// Result: Right("Sales Rep")

383

```

384

385

### Finding Elements

386

387

```scala

388

import io.circe._

389

import io.circe.parser._

390

391

val json = parse("""

392

{

393

"users": [

394

{"name": "John", "active": true},

395

{"name": "Jane", "active": false},

396

{"name": "Bob", "active": true}

397

]

398

}

399

""").getOrElse(Json.Null)

400

401

val cursor = json.hcursor

402

403

// Find first active user

404

val activeUserCursor = cursor

405

.downField("users")

406

.downArray

407

.find(_.hcursor.downField("active").as[Boolean].contains(true))

408

409

val activeUserName = activeUserCursor.downField("name").as[String]

410

// Result: Right("John")

411

412

// Find by name

413

val janeCursor = cursor

414

.downField("users")

415

.downArray

416

.find(_.hcursor.downField("name").as[String].contains("Jane"))

417

418

val janeActive = janeCursor.downField("active").as[Boolean]

419

// Result: Right(false)

420

```

421

422

### Path Tracking

423

424

```scala

425

import io.circe._

426

import io.circe.parser._

427

428

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

429

val cursor = json.hcursor

430

431

val nameCursor = cursor

432

.downField("user")

433

.downField("profile")

434

.downField("name")

435

436

// Get path information

437

println(nameCursor.pathString)

438

// Result: ".user.profile.name"

439

440

// Get operation history

441

println(nameCursor.history)

442

// Result: List(DownField(user), DownField(profile), DownField(name))

443

444

// Failed navigation path tracking

445

val failedCursor = cursor.downField("nonexistent").downField("field")

446

println(failedCursor.pathString)

447

// Result: ".nonexistent.field"

448

```

449

450

### Cursor Utilities

451

452

```scala

453

import io.circe._

454

455

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

456

457

// Helper methods for safe access

458

def getName(c: HCursor): String =

459

c.get[String]("name").getOrElse("Unknown")

460

461

def getAge(c: HCursor): Int =

462

c.getOrElse[Int]("age")(0)

463

464

// Replay operations on different JSON

465

val operations = List(CursorOp.DownField("user"), CursorOp.DownField("name"))

466

val newJson = Json.obj("user" -> Json.obj("name" -> Json.fromString("Jane")))

467

val replayedCursor = newJson.hcursor.replay(operations)

468

// Result: cursor positioned at "Jane"

469

```