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

error-handling.mddocs/

0

# Error Handling

1

2

Circe provides comprehensive error handling with detailed failure information for both parsing and decoding operations. The error system tracks operation history and provides clear error messages.

3

4

## Error

5

6

Base class for all circe errors.

7

8

```scala { .api }

9

sealed abstract class Error extends Exception {

10

override def fillInStackTrace(): Throwable = this

11

}

12

```

13

14

## ParsingFailure

15

16

Represents JSON parsing failures from malformed JSON strings.

17

18

```scala { .api }

19

final case class ParsingFailure(message: String, underlying: Throwable) extends Error {

20

override def getMessage: String = message

21

override def toString(): String = s"ParsingFailure: $message"

22

}

23

```

24

25

### ParsingFailure Companion Object

26

27

```scala { .api }

28

object ParsingFailure {

29

implicit val eqParsingFailure: Eq[ParsingFailure]

30

implicit val showParsingFailure: Show[ParsingFailure]

31

}

32

```

33

34

## DecodingFailure

35

36

Represents JSON decoding failures with detailed path and error information.

37

38

```scala { .api }

39

sealed abstract class DecodingFailure extends Error {

40

// Error information

41

def message: String

42

def history: List[CursorOp]

43

def reason: DecodingFailure.Reason

44

def pathToRootString: Option[String]

45

46

// Message creation

47

override def getMessage: String

48

override def toString: String

49

50

// Error manipulation

51

def copy(message: String = message, history: => List[CursorOp] = history): DecodingFailure

52

def withMessage(message: String): DecodingFailure

53

def withReason(reason: DecodingFailure.Reason): DecodingFailure

54

}

55

```

56

57

### DecodingFailure Companion Object

58

59

```scala { .api }

60

object DecodingFailure {

61

// Constructors

62

def apply(message: String, ops: => List[CursorOp]): DecodingFailure

63

def apply(reason: Reason, ops: => List[CursorOp]): DecodingFailure

64

def apply(reason: Reason, cursor: ACursor): DecodingFailure

65

def fromThrowable(t: Throwable, ops: => List[CursorOp]): DecodingFailure

66

67

// Pattern matching support

68

def unapply(error: Error): Option[(String, List[CursorOp])]

69

70

// Type class instances

71

implicit val eqDecodingFailure: Eq[DecodingFailure]

72

implicit val showDecodingFailure: Show[DecodingFailure]

73

74

// Failure reasons

75

sealed abstract class Reason

76

object Reason {

77

case object MissingField extends Reason

78

case class WrongTypeExpectation(expectedJsonFieldType: String, jsonValue: Json) extends Reason

79

case class CustomReason(message: String) extends Reason

80

}

81

}

82

```

83

84

## Errors

85

86

Convenience exception for aggregating multiple errors.

87

88

```scala { .api }

89

final case class Errors(errors: NonEmptyList[Error]) extends Exception {

90

def toList: List[Error]

91

override def fillInStackTrace(): Throwable = this

92

}

93

```

94

95

## Error Companion Object

96

97

```scala { .api }

98

object Error {

99

implicit val eqError: Eq[Error]

100

implicit val showError: Show[Error]

101

}

102

```

103

104

## Usage Examples

105

106

### Parsing Errors

107

108

```scala

109

import io.circe._

110

import io.circe.parser._

111

112

// Invalid JSON string

113

val malformedJson = """{"name": "John", "age":}"""

114

115

parse(malformedJson) match {

116

case Left(ParsingFailure(message, underlying)) =>

117

println(s"Parsing failed: $message")

118

println(s"Underlying cause: ${underlying.getMessage}")

119

case Right(json) =>

120

println("Parsing succeeded")

121

}

122

123

// Result: ParsingFailure: expected json value got '}' (line 1, column 24)

124

```

125

126

### Decoding Errors

127

128

```scala

129

import io.circe._

130

import io.circe.parser._

131

132

case class Person(name: String, age: Int, email: String)

133

134

implicit val personDecoder: Decoder[Person] = Decoder.forProduct3("name", "age", "email")(Person.apply)

135

136

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

137

138

json.as[Person] match {

139

case Left(DecodingFailure(message, history)) =>

140

println(s"Decoding failed: $message")

141

println(s"Path: ${CursorOp.opsToPath(history)}")

142

println(s"History: $history")

143

case Right(person) =>

144

println(s"Decoded: $person")

145

}

146

147

// Result:

148

// Decoding failed: Got value '"thirty"' with wrong type, expecting number

149

// Path: .age

150

// History: List(DownField(age))

151

```

152

153

### Missing Field Errors

154

155

```scala

156

import io.circe._

157

import io.circe.parser._

158

159

case class Person(name: String, age: Int, email: String)

160

implicit val personDecoder: Decoder[Person] = Decoder.forProduct3("name", "age", "email")(Person.apply)

161

162

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

163

164

json.as[Person] match {

165

case Left(failure @ DecodingFailure(message, history)) =>

166

println(s"Missing field error: $message")

167

println(s"Reason: ${failure.reason}")

168

failure.reason match {

169

case DecodingFailure.Reason.MissingField =>

170

println("Field is missing from JSON")

171

case DecodingFailure.Reason.WrongTypeExpectation(expected, actual) =>

172

println(s"Expected $expected but got ${actual.name}")

173

case DecodingFailure.Reason.CustomReason(msg) =>

174

println(s"Custom error: $msg")

175

}

176

case Right(person) =>

177

println(s"Decoded: $person")

178

}

179

```

180

181

### Custom Error Messages

182

183

```scala

184

import io.circe._

185

186

implicit val positiveIntDecoder: Decoder[Int] = Decoder[Int]

187

.ensure(_ > 0, "Age must be positive")

188

.withErrorMessage("Invalid age value")

189

190

val negativeAge = Json.fromInt(-5)

191

negativeAge.as[Int] match {

192

case Left(failure) =>

193

println(failure.message) // "Invalid age value"

194

case Right(age) =>

195

println(s"Age: $age")

196

}

197

```

198

199

### Validation Errors

200

201

```scala

202

import io.circe._

203

204

case class Email(value: String)

205

206

implicit val emailDecoder: Decoder[Email] = Decoder[String].emap { str =>

207

if (str.contains("@") && str.contains("."))

208

Right(Email(str))

209

else

210

Left("Invalid email format")

211

}

212

213

val invalidEmail = Json.fromString("notanemail")

214

invalidEmail.as[Email] match {

215

case Left(failure) =>

216

println(s"Validation failed: ${failure.message}")

217

case Right(email) =>

218

println(s"Valid email: ${email.value}")

219

}

220

```

221

222

### Accumulating Errors

223

224

```scala

225

import io.circe._

226

import io.circe.parser._

227

import cats.data.ValidatedNel

228

import cats.syntax.apply._

229

230

case class Person(name: String, age: Int, email: String)

231

232

implicit val nameDecoder: Decoder[String] = Decoder[String].ensure(_.nonEmpty, "Name cannot be empty")

233

implicit val ageDecoder: Decoder[Int] = Decoder[Int].ensure(_ > 0, "Age must be positive")

234

implicit val emailDecoder: Decoder[String] = Decoder[String].emap { str =>

235

if (str.contains("@")) Right(str) else Left("Invalid email")

236

}

237

238

implicit val personDecoder: Decoder[Person] = new Decoder[Person] {

239

def apply(c: HCursor): Decoder.Result[Person] = {

240

(

241

c.downField("name").as[String](nameDecoder),

242

c.downField("age").as[Int](ageDecoder),

243

c.downField("email").as[String](emailDecoder)

244

).mapN(Person.apply)

245

}

246

247

override def decodeAccumulating(c: HCursor): Decoder.AccumulatingResult[Person] = {

248

(

249

c.downField("name").as[String](nameDecoder).toValidatedNel,

250

c.downField("age").as[Int](ageDecoder).toValidatedNel,

251

c.downField("email").as[String](emailDecoder).toValidatedNel

252

).mapN(Person.apply)

253

}

254

}

255

256

val badJson = parse("""{"name": "", "age": -5, "email": "invalid"}""").getOrElse(Json.Null)

257

258

badJson.asAccumulating[Person] match {

259

case cats.data.Valid(person) =>

260

println(s"Decoded: $person")

261

case cats.data.Invalid(errors) =>

262

println("Multiple errors:")

263

errors.toList.foreach(error => println(s" - ${error.message}"))

264

}

265

266

// Result:

267

// Multiple errors:

268

// - Name cannot be empty

269

// - Age must be positive

270

// - Invalid email

271

```

272

273

### Error Recovery

274

275

```scala

276

import io.circe._

277

278

// Try multiple decoders

279

implicit val flexibleStringDecoder: Decoder[String] =

280

Decoder[String] or Decoder[Int].map(_.toString) or Decoder[Boolean].map(_.toString)

281

282

// Recover from specific errors

283

implicit val recoveryDecoder: Decoder[Int] = Decoder[Int].handleErrorWith { failure =>

284

failure.reason match {

285

case DecodingFailure.Reason.WrongTypeExpectation(_, json) if json.isString =>

286

Decoder[String].emap(str =>

287

try Right(str.toInt)

288

catch { case _: NumberFormatException => Left("Not a number") }

289

)

290

case _ => Decoder.failed(failure)

291

}

292

}

293

294

val stringNumber = Json.fromString("42")

295

stringNumber.as[Int](recoveryDecoder) match {

296

case Right(num) => println(s"Recovered: $num") // "Recovered: 42"

297

case Left(error) => println(s"Failed: ${error.message}")

298

}

299

```

300

301

### Path Information

302

303

```scala

304

import io.circe._

305

import io.circe.parser._

306

307

val nestedJson = parse("""

308

{

309

"users": [

310

{"profile": {"name": "John", "age": "invalid"}}

311

]

312

}

313

""").getOrElse(Json.Null)

314

315

val cursor = nestedJson.hcursor

316

val result = cursor

317

.downField("users")

318

.downN(0)

319

.downField("profile")

320

.downField("age")

321

.as[Int]

322

323

result match {

324

case Left(failure) =>

325

println(s"Error at path: ${failure.pathToRootString.getOrElse("unknown")}")

326

println(s"Operation history: ${failure.history}")

327

println(s"Cursor path: ${CursorOp.opsToPath(failure.history)}")

328

case Right(age) =>

329

println(s"Age: $age")

330

}

331

332

// Result:

333

// Error at path: .users[0].profile.age

334

// Operation history: List(DownField(users), DownN(0), DownField(profile), DownField(age))

335

// Cursor path: .users[0].profile.age

336

```

337

338

### Custom Error Types

339

340

```scala

341

import io.circe._

342

343

sealed trait ValidationError

344

case class TooYoung(age: Int) extends ValidationError

345

case class InvalidEmail(email: String) extends ValidationError

346

347

implicit val ageDecoder: Decoder[Int] = Decoder[Int].emap { age =>

348

if (age >= 18) Right(age)

349

else Left(s"Too young: $age")

350

}

351

352

implicit val emailDecoder: Decoder[String] = Decoder[String].ensure(

353

email => email.contains("@") && email.length > 5,

354

"Invalid email format"

355

)

356

357

case class User(age: Int, email: String)

358

implicit val userDecoder: Decoder[User] = Decoder.forProduct2("age", "email")(User.apply)

359

360

val json = parse("""{"age": 16, "email": "bad"}""").getOrElse(Json.Null)

361

json.as[User] match {

362

case Left(failure) =>

363

println(s"Validation failed: ${failure.message}")

364

println(s"At: ${failure.pathToRootString.getOrElse("root")}")

365

case Right(user) =>

366

println(s"Valid user: $user")

367

}

368

```

369

370

### Working with Either and Option

371

372

```scala

373

import io.circe._

374

375

// Decoder that never fails

376

implicit val safeStringDecoder: Decoder[Option[String]] =

377

Decoder[String].map(Some(_)).handleErrorWith(_ => Decoder.const(None))

378

379

// Either for multiple possible types

380

implicit val stringOrIntDecoder: Decoder[Either[String, Int]] =

381

Decoder[String].map(Left(_)) or Decoder[Int].map(Right(_))

382

383

val json = Json.fromInt(42)

384

385

json.as[Option[String]] match {

386

case Right(None) => println("Not a string, but that's OK")

387

case Right(Some(str)) => println(s"String: $str")

388

case Left(_) => println("This shouldn't happen with safe decoder")

389

}

390

391

json.as[Either[String, Int]] match {

392

case Right(Left(str)) => println(s"String: $str")

393

case Right(Right(num)) => println(s"Number: $num")

394

case Left(error) => println(s"Neither string nor int: ${error.message}")

395

}

396

```