Scala Circe JSON Processing
SkillDev toolsWorking with Circe JSON library in Scala for type-safe JSON processing
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Scala Circe JSON Processing skill
What this skill tells your AI
The instructions your AI receives, as published by cxcscmu/skilllearnbench in skills/b1-one-shot-claude-haiku-4-5/python-scala-translation/scala-circe-json/SKILL.md and read by ahel’s review.
Overview
Circe is the standard type-safe JSON library in Scala. It provides:
- Immutable
Jsontype - Type-safe codecs (Encoder/Decoder)
- Cursor-based navigation (like XPath for JSON)
- Pretty printing
- Seamless case class integration
Installation
Add to build.sbt:
libraryDependencies ++= Seq(
"io.circe" %% "circe-core" % "0.14.5",
"io.circe" %% "circe-parser" % "0.14.5",
"io.circe" %% "circe-generic" % "0.14.5"
)
Basic Usage
Parsing JSON
import io.circe.parser._
import io.circe._
// Parse from string
val jsonString = """{"name": "Alice", "age": 30}"""
val result: Either[io.circe.ParsingFailure, Json] = parse(jsonString)
// Safe extraction
result match {
case Right(json) => println(s"Parsed: $json")
case Left(error) => println(s"Parse error: $error")
}
// Or with Try
val json: scala.util.Try[Json] = parse(jsonString).toTry
Creating JSON
import io.circe.syntax._
// From primitives
val str = Json.fromString("hello")
val num = Json.fromInt(42)
val bool = Json.fromBoolean(true)
val null_val = Json.Null
// Object (using syntax extension)
val obj = Json.obj(
"name" -> Json.fromString("Alice"),
"age" -> Json.fromInt(30)
)
// Array
val arr = Json.arr(
Json.fromInt(1),
Json.fromInt(2),
Json.fromInt(3)
)
// Using tuple extension (cleaner)
val obj = Json.obj(
"name" -> "Alice".asJson,
"age" -> 30.asJson,
"scores" -> List(90, 85, 88).asJson
)
Working with Json Type
val json = Json.obj(
"name" -> "Alice".asJson,
"age" -> 30.asJson
)
// Type checks
json.isObject // true
json.isArray // false
json.isNull // false
// Conversions
json.asObject // Option[JsonObject]
json.asArray // Option[Vector[Json]]
json.asString // Option[String]
json.asNumber // Option[JsonNumber]
json.asBoolean // Option[Boolean]
// Unsafe conversion (throws)
json.asObject.get
Navigation with Cursors
Cursors provide safe, composable navigation (like XPath):
import io.circe.{Json, HCursor}
val json = parse("""
{
"person": {
"name": "Alice",
"addresses": [
{"city": "NYC"},
{"city": "Boston"}
]
}
}
""").getOrElse(Json.Null)
// Creating cursor
val cursor: HCursor = json.hcursor
// Navigation
val name: Either[DecodingFailure, String] =
cursor
.downField("person")
.downField("name")
.as[String]
// Navigate arrays
val firstCity: Either[DecodingFailure, String] =
cursor
.downField("person")
.downField("addresses")
.downArray
.downField("city")
.as[String]
// Optional navigation (returns Json)
cursor.downField("person").downField("name").focus // Option[Json]
Cursor Methods
val cursor = json.hcursor
// Navigation
cursor.downField("fieldName") // Navigate to field
cursor.downArray // Navigate to first array element
cursor.up // Go to parent
cursor.left // Move to previous array element
cursor.right // Move to next array element
// Extraction
cursor.focus // Option[Json] - current value
cursor.as[String] // Either[DecodingFailure, String]
// Checking
cursor.succeeded // Boolean - did navigation succeed?
Type-Safe Encoding/Decoding
For Case Classes
import io.circe._, io.circe.generic.semiauto._
case class Person(name: String, age: Int)
// Automatic codec generation
implicit val encoder: Encoder[Person] = deriveEncoder[Person]
implicit val decoder: Decoder[Person] = deriveDecoder[Person]
// Usage
val person = Person("Alice", 30)
val json = person.asJson
val decoded = json.as[Person] // Either[DecodingFailure, Person]
Custom Codecs
implicit val customEncoder: Encoder[Person] = new Encoder[Person] {
def apply(p: Person): Json = Json.obj(
"fullName" -> Json.fromString(p.name),
"years" -> Json.fromInt(p.age)
)
}
implicit val customDecoder: Decoder[Person] = new Decoder[Person] {
def apply(c: HCursor): Decoder.Result[Person] = for {
name <- c.downField("fullName").as[String]
age <- c.downField("years").as[Int]
} yield Person(name, age)
}
Recursive JSON Structures
For recursive types like JSON itself:
// Representing JSON recursively
sealed trait JsonValue
case class JString(value: String) extends JsonValue
case class JNumber(value: Double) extends JsonValue
case class JBoolean(value: Boolean) extends JsonValue
case object JNull extends JsonValue
case class JArray(value: Vector[JsonValue]) extends JsonValue
case class JObject(value: Map[String, JsonValue]) extends JsonValue
// Or use Circe's Json type directly (which IS recursive)
val json: Json = Json.obj(
"data" -> Json.arr(
Json.obj("id" -> 1.asJson),
Json.obj("id" -> 2.asJson)
)
)
Pretty Printing
import io.circe.Printer
val json = Json.obj("name" -> "Alice".asJson, "age" -> 30.asJson)
// Compact
json.toString()
// {"name":"Alice","age":30}
// Pretty printed
val printer = Printer.spaces2 // 2-space indent
println(printer.print(json))
// {
// "name" : "Alice",
// "age" : 30
// }
// Custom printer
val customPrinter = Printer(
dropNullValues = false,
indent = " "
)
JSON Path Extraction (for tokenize_path)
def getValueAtPath(json: Json, path: String): Option[Json] = {
val parts = path.split("\\.")
parts.foldLeft(Option(json)) { case (current, part) =>
current.flatMap { j =>
if (j.isObject) {
j.hcursor.downField(part).focus
} else if (j.isArray) {
part.toIntOption.flatMap { idx =>
j.asArray.flatMap(_.lift(idx))
}
} else {
None
}
}
}
}
// Usage
val json = parse("""{"person": {"name": "Alice", "scores": [90, 85]}}""").getOrElse(Json.Null)
val name = getValueAtPath(json, "person.name") // Some("Alice")
val score = getValueAtPath(json, "person.scores.0") // Some(90)
val missing = getValueAtPath(json, "person.email") // None
Common Patterns for Tokenizer
JSON Tokenization
import io.circe.Json
import io.circe.syntax._
class JsonTokenizer(pretty: Boolean = false) {
def tokenize(json: Json): Token = {
val jsonStr = if (pretty) {
Printer.spaces2.print(json)
} else {
json.toString()
}
Token(jsonStr, TokenType.STRUCTURED, Map("json" -> true.asJson))
}
def tokenizePath(json: Json, path: String): Option[Token] = {
getValueAtPath(json, path).map(tokenize)
}
private def getValueAtPath(json: Json, path: String): Option[Json] = {
val parts = path.split("\\.")
parts.foldLeft(Option(json)) { case (current, part) =>
current.flatMap { j =>
if (j.isObject) j.hcursor.downField(part).focus
else if (j.isArray) {
part.toIntOption.flatMap(idx => j.asArray.flatMap(_.lift(idx)))
} else None
}
}
}
}
Type Safety Benefits
Unlike Python's duck typing with JSON:
# Python - runtime errors possible
data = {"name": "Alice"}
age = data["age"] # KeyError!
// Scala - compile-time safety
val json = parse("""{"name": "Alice"}""").getOrElse(Json.Null)
val age: Option[Int] = json.hcursor.downField("age").as[Int].toOption
// Type mismatch caught at compile time
Circe vs Manual JSON Handling
// Manual approach (error-prone)
val jsonStr = s"""{"value": "$value", "type": "$tokenType"}"""
// Circe approach (safe, composable)
val token = Token(value, tokenType)
val json = token.asJson // With derived encoders
Signals
- GitHub stars
- 83
- Forks
- 5
- Last commit
- Jul 2026
Advanced
- Catalog kind
- skill
- Gateway key
scala-circe-json- Source
- github.com/cxcscmu/skilllearnbench