jsondecode.com logo

JSON in Scala — play-json, circe & spray-json

Scala does not include JSON support in its standard library, but three libraries dominate: play-json (part of the Play Framework ecosystem, widely used standalone), circe (functional, typeclass-based, excellent for cats users), and spray-json (lightweight, protocol-based).

play-json — parse and write JSON

Add com.typesafe.play:play-json to your build. Use Json.parse() to read and Json.stringify() to write.

import play.api.libs.json._

val jsonStr = """{"id":1,"name":"Alice","email":"alice@example.com"}"""
val json: JsValue = Json.parse(jsonStr)

// Read a field
val name = (json \ "name").as[String]  // Alice

// Serialise back to string
val compact  = Json.stringify(json)
val pretty   = Json.prettyPrint(json)

play-json — Reads and Writes for case classes

Define implicit Reads[T] and Writes[T] (or Format[T]) to convert between JSON and your case classes. The macro Json.format[T] generates both automatically.

import play.api.libs.json._

case class User(id: Int, name: String, email: String)

object User {
  implicit val format: OFormat[User] = Json.format[User]
}

// Decode
val user: User = Json.parse(jsonStr).as[User]

// Encode
val out: JsValue = Json.toJson(user)
println(Json.stringify(out))

circe — automatic derivation

Add io.circe:circe-core, circe-generic, and circe-parser. Import io.circe.generic.auto._ for automatic codec derivation from case classes.

import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._

case class User(id: Int, name: String, email: String)

// Decode — returns Either[Error, User]
val result: Either[Error, User] = decode[User](jsonStr)
result match {
  case Right(user) => println(user.name)  // Alice
  case Left(err)   => println(s"Error: $err")
}

// Encode
val user = User(1, "Alice", "alice@example.com")
println(user.asJson.noSpaces)

circe — cursor-based field access

Use circe's HCursor to navigate and extract individual fields without decoding to a case class.

import io.circe.parser._

val cursor = parse(jsonStr).getOrElse(Json.Null).hcursor

val name  = cursor.downField("name").as[String]  // Right("Alice")
val id    = cursor.downField("id").as[Int]        // Right(1)

// Nested field
val city = cursor
  .downField("address")
  .downField("city")
  .as[String]

spray-json — protocol-based

Add io.spray:spray-json. Define a JsonFormat using jsonFormat helpers and import the DefaultJsonProtocol.

import spray.json._
import DefaultJsonProtocol._

case class User(id: Int, name: String, email: String)

object UserProtocol extends DefaultJsonProtocol {
  implicit val userFormat: RootJsonFormat[User] = jsonFormat3(User)
}

import UserProtocol._

// Parse
val user: User = jsonStr.parseJson.convertTo[User]

// Serialise
val json: String = user.toJson.prettyPrint

Frequently Asked Questions

Which Scala JSON library should I use?

Use play-json if you are in the Play Framework ecosystem or need a battle-tested standalone library. Use circe if you are already using cats/cats-effect or want a purely functional, typeclass-based approach. Use spray-json for lightweight Akka HTTP services.

How do I parse JSON in Scala with circe?

Add circe-parser to your build and call io.circe.parser.decode[MyType](jsonString). It returns Either[io.circe.Error, MyType]. Import io.circe.generic.auto._ for automatic case class derivation.

How do I auto-generate JSON codecs for case classes in Scala?

With circe, import io.circe.generic.auto._ and Encoder/Decoder instances are automatically derived. With play-json, use Json.format[MyCaseClass] in the companion object. With spray-json, use jsonFormat(N) helpers.

How do I handle optional fields in Scala JSON?

Declare the field as Option[T] in your case class. All three major libraries (play-json, circe, spray-json) treat missing or null JSON keys as None and serialise None as an absent key (or null, depending on configuration).

How do I read a single field from JSON in Scala without a full case class?

With circe, use the HCursor API: parse(json).getOrElse(Json.Null).hcursor.downField("key").as[String]. With play-json, use (Json.parse(json) \ "key").as[String].

Format and validate your JSON instantly

Free, no ads, no sign-up. Also converts JSON to TypeScript, YAML, CSV, and more.

Open JSON Formatter →

If jsondecode.com saved you time, share it with your team

Free forever. No ads. No sign-up. Help other developers find it.