DEV Community

Fulton Byrne
Fulton Byrne

Posted on

Parsing JSON with Circe

I received some very good feedback on my last post: Scala has some popular libraries that should be avoided. Not only that, I learned a few libraries I considered before required more investigation. Today I want to look at Circe. After working in Scala a bit, I understand the problems it wants to solve and why the project makes the choices it does.

Cursors

Think about how you use JSON for a minute. Programs retrieve or assign to a single field at a time. Parsing the entire document when the program only needs one field wastes processing time and heap memory.

car["age"] = 23
console.log(car["age"])
Enter fullscreen mode Exit fullscreen mode

When I wrote more NodeJS I would not bat an eye at parsing one document entirely, but when working with many documents or a large document I would use JSONStream to selectively parse items out of the document. This provides two benefits: it does not require storing the entire document into memory and only the pertinent fields parse.

Circe provides "cursors", an API to traverse raw JSON and project the values therein. Think about cursors the way you think about the blinking | cursor when writing an email or text. It keeps track of your place in the document, moves around the document to track the new current location, and even selects interesting text for modification.

Example

I'm assuming some sbt knowledge. Here's the build.sbt I used to run this project.

name := "basic_json"

version := "0.1"

scalaVersion := "2.12.2"

lazy val circeVersion = "0.12.2"


libraryDependencies ++= Seq(
  "io.circe" %% "circe-parser" % circeVersion,
  "io.circe" %% "circe-core" % circeVersion,
  "io.circe" %% "circe-generic" % circeVersion
)
Enter fullscreen mode Exit fullscreen mode

This example iterates through a JSON document via a cursor to select and present the car's age the document describes.

import io.circe.parser._
object Main extends App {
  val rawJson: String =
    """
      |{
      |  "hello": "world",
      |  "car": {
      |    "type": "honda",
      |    "age": 23,
      |    "color": "blue",
      |    "used": true,
      |    "owner": {
      |       "name": "darwin",
      |       "age": 43
      |    }
      |  }
      |}
      |""".stripMargin

  val parsed = parse(rawJson) match {
    case Left(s) => s
    case Right(r) => r.hcursor.downField("car").get[Int]("age") match {
      case Right(age) => age
      case Left(l) => l
    }
  }

  println(parsed)

}
Enter fullscreen mode Exit fullscreen mode

A lot going on, but focusing on the critical area simplifies things:

  val parsed = parse(rawJson) match {
    case Left(s) => s
    case Right(r) => r.hcursor.downField("car").get[Int]("age") match {
      case Right(age) => age
      case Left(l) => l
    }
  }
Enter fullscreen mode Exit fullscreen mode

Circe's parse from import io.circe.parser._ ingests the raw JSON document and returns an Either. Left contains useful information about failures, while Right contains success values. I always return the unwrapped Left value since I just want to log results, but ideally Lefts would branch code into an error handling branch.

Finally, the important piece: actually retrieving the data.

r.hcursor.downField("car").get[Int]("age")
Enter fullscreen mode Exit fullscreen mode

r represents the Right result of parse which contains an hcursor into the JSON document. HCursors maintain a cursor's history so relevant history may surface during an error indicating why the cursor operation failed.

downField("x") moves the cursor's position into the JSON object specified by "x". Very similar to how JavaScript accesses object via obj["x"]. get[Type]("field") performs a downField to the "field" key, parses the JSON object at that point, and then casts it to the type specified. In my case I cast the "age" field to an Int. Casting tells Scala to take an object it thought was text and convert it into a Scala object like an Int.

Conclusion

Scala definitely requires more thought when working with JSON. While Circe's techniques seem radically different from Javascript at first glance; these languages really have a lot in common. As an exercise try triggering different Left branches to see the messages they produce.

Latest comments (0)