Documentation

You are viewing the documentation for the 2.8.18 release in the 2.8.x series of releases. The latest stable release series is 3.0.x.

§JSON basics

Modern web applications often need to parse and generate data in the JSON (JavaScript Object Notation) format. Play supports this via its JSON library.

JSON is a lightweight data-interchange format and looks like this:

{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}

To learn more about JSON, see json.org.

§The Play JSON library

The play.api.libs.json package contains data structures for representing JSON data and utilities for converting between these data structures and other data representations. Some of the features of this package are:

The package provides the following types:

§JsValue

This is a trait representing any JSON value. The JSON library has a case class extending JsValue to represent each valid JSON type:

Using the various JsValue types, you can construct a representation of any JSON structure.

§Json

The Json object provides utilities, primarily for conversion to and from JsValue structures.

§JsPath

Represents a path into a JsValue structure, analogous to XPath for XML. This is used for traversing JsValue structures and in patterns for implicit converters.

§Converting to a JsValue

§Using string parsing

import play.api.libs.json._

val json: JsValue = Json.parse("""
  {
    "name" : "Watership Down",
    "location" : {
      "lat" : 51.235685,
      "long" : -1.309197
    },
    "residents" : [ {
      "name" : "Fiver",
      "age" : 4,
      "role" : null
    }, {
      "name" : "Bigwig",
      "age" : 6,
      "role" : "Owsla"
    } ]
  }
  """)

§Using class construction

import play.api.libs.json._

val json: JsValue = JsObject(
  Seq(
    "name"     -> JsString("Watership Down"),
    "location" -> JsObject(Seq("lat" -> JsNumber(51.235685), "long" -> JsNumber(-1.309197))),
    "residents" -> JsArray(
      IndexedSeq(
        JsObject(
          Seq(
            "name" -> JsString("Fiver"),
            "age"  -> JsNumber(4),
            "role" -> JsNull
          )
        ),
        JsObject(
          Seq(
            "name" -> JsString("Bigwig"),
            "age"  -> JsNumber(6),
            "role" -> JsString("Owsla")
          )
        )
      )
    )
  )
)

Json.obj and Json.arr can simplify construction a bit. Note that most values don’t need to be explicitly wrapped by JsValue classes, the factory methods use implicit conversion (more on this below).

import play.api.libs.json.JsNull
import play.api.libs.json.Json
import play.api.libs.json.JsString
import play.api.libs.json.JsValue

val json: JsValue = Json.obj(
  "name"     -> "Watership Down",
  "location" -> Json.obj("lat" -> 51.235685, "long" -> -1.309197),
  "residents" -> Json.arr(
    Json.obj(
      "name" -> "Fiver",
      "age"  -> 4,
      "role" -> JsNull
    ),
    Json.obj(
      "name" -> "Bigwig",
      "age"  -> 6,
      "role" -> "Owsla"
    )
  )
)

§Using Writes converters

Scala to JsValue conversion is performed by the utility method Json.toJson[T](T)(implicit writes: Writes[T]). This functionality depends on a converter of type Writes[T] which can convert a T to a JsValue.

The Play JSON API provides implicit Writes for most basic types, such as Int, Double, String, and Boolean. It also supports Writes for collections of any type T that a Writes[T] exists.

import play.api.libs.json._

// basic types
val jsonString  = Json.toJson("Fiver")
val jsonNumber  = Json.toJson(4)
val jsonBoolean = Json.toJson(false)

// collections of basic types
val jsonArrayOfInts    = Json.toJson(Seq(1, 2, 3, 4))
val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))

To convert your own models to JsValues, you must define implicit Writes converters and provide them in scope.

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._

implicit val locationWrites = new Writes[Location] {
  def writes(location: Location) = Json.obj(
    "lat"  -> location.lat,
    "long" -> location.long
  )
}

implicit val residentWrites = new Writes[Resident] {
  def writes(resident: Resident) = Json.obj(
    "name" -> resident.name,
    "age"  -> resident.age,
    "role" -> resident.role
  )
}

implicit val placeWrites = new Writes[Place] {
  def writes(place: Place) = Json.obj(
    "name"      -> place.name,
    "location"  -> place.location,
    "residents" -> place.residents
  )
}

val place = Place(
  "Watership Down",
  Location(51.235685, -1.309197),
  Seq(
    Resident("Fiver", 4, None),
    Resident("Bigwig", 6, Some("Owsla"))
  )
)

val json = Json.toJson(place)

Alternatively, you can define your Writes using the combinator pattern:

Note: The combinator pattern is covered in detail in JSON Reads/Writes/Formats Combinators.

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
    (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val residentWrites: Writes[Resident] = (
  (JsPath \ "name").write[String] and
    (JsPath \ "age").write[Int] and
    (JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))

implicit val placeWrites: Writes[Place] = (
  (JsPath \ "name").write[String] and
    (JsPath \ "location").write[Location] and
    (JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))

§Traversing a JsValue structure

You can traverse a JsValue structure and extract specific values. The syntax and functionality is similar to Scala XML processing.

Note: The following examples are applied to the JsValue structure created in previous examples.

§Simple path \

Applying the \ operator to a JsValue will return the property corresponding to the field argument in a JsObject, or the item at that index in a JsArray

val lat = (json \ "location" \ "lat").get
// returns JsNumber(51.235685)
val bigwig = (json \ "residents" \ 1).get
// returns {"name":"Bigwig","age":6,"role":"Owsla"}

The \ operator returns a JsLookupResult, which is either JsDefined or JsUndefined. You can chain multiple \ operators, and the result will be JsUndefined if any intermediate value cannot be found. Calling get on a JsLookupResult attempts to get the value if it is defined and throws an exception if it is not.

You can also use the Direct lookup apply method (below) to get a field in an object or index in an array. Like get, this method will throw an exception if the value does not exist.

§Recursive path \\

Applying the \\ operator will do a lookup for the field in the current object and all descendants.

val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))

§Direct lookup

You can retrieve a value in a JsArray or JsObject using an .apply operator, which is identical to the Simple path \ operator except it returns the value directly (rather than wrapping it in a JsLookupResult) and throws an exception if the index or key is not found:

val name = json("name")
// returns JsString("Watership Down")

val bigwig2 = json("residents")(1)
// returns {"name":"Bigwig","age":6,"role":"Owsla"}

// (json("residents")(3)
// throws an IndexOutOfBoundsException

// json("bogus")
// throws a NoSuchElementException

This is useful if you are writing quick-and-dirty code and are accessing some JSON values you know to exist, for example in one-off scripts or in the REPL.

§Converting from a JsValue

§Using String utilities

Minified:

val minifiedString: String = Json.stringify(json)
{"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197},"residents":[{"name":"Fiver","age":4,"role":null},{"name":"Bigwig","age":6,"role":"Owsla"}]}

Readable:

val readableString: String = Json.prettyPrint(json)
{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}

§Using JsValue.as/asOpt

The simplest way to convert a JsValue to another type is using JsValue.as[T](implicit fjs: Reads[T]): T. This requires an implicit converter of type Reads[T] to convert a JsValue to T (the inverse of Writes[T]). As with Writes, the JSON API provides Reads for basic types.

val name = (json \ "name").as[String]
// "Watership Down"

val names = (json \\ "name").map(_.as[String])
// Seq("Watership Down", "Fiver", "Bigwig")

The as method will throw a JsResultException if the path is not found or the conversion is not possible. A safer method is JsValue.asOpt[T](implicit fjs: Reads[T]): Option[T].

val nameOption = (json \ "name").asOpt[String]
// Some("Watership Down")

val bogusOption = (json \ "bogus").asOpt[String]
// None

Although the asOpt method is safer, any error information is lost.

§Using validation

The preferred way to convert from a JsValue to another type is by using its validate method (which takes an argument of type Reads). This performs both validation and conversion, returning a type of JsResult. JsResult is implemented by two classes:

You can apply various patterns for handling a validation result:

val json = { ... }

val nameResult: JsResult[String] = (json \ "name").validate[String]

// Pattern matching
nameResult match {
  case JsSuccess(name, _) => println(s"Name: $name")
  case e: JsError         => println(s"Errors: ${JsError.toJson(e)}")
}

// Fallback value
val nameOrFallback = nameResult.getOrElse("Undefined")

// map
val nameUpperResult: JsResult[String] = nameResult.map(_.toUpperCase)

// fold
val nameOption: Option[String] = nameResult.fold(
  invalid = { fieldErrors =>
    fieldErrors.foreach { x =>
      println(s"field: ${x._1}, errors: ${x._2}")
    }
    Option.empty[String]
  },
  valid = Some(_)
)

§JsValue to a model

To convert from JsValue to a model, you must define implicit Reads[T] where T is the type of your model.

Note: The pattern used to implement Reads and custom validation are covered in detail in JSON Reads/Writes/Formats Combinators.

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
    (JsPath \ "long").read[Double]
)(Location.apply _)

implicit val residentReads: Reads[Resident] = (
  (JsPath \ "name").read[String] and
    (JsPath \ "age").read[Int] and
    (JsPath \ "role").readNullable[String]
)(Resident.apply _)

implicit val placeReads: Reads[Place] = (
  (JsPath \ "name").read[String] and
    (JsPath \ "location").read[Location] and
    (JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)

val json = { ... }

val placeResult: JsResult[Place] = json.validate[Place]
// JsSuccess(Place(...),)

val residentResult: JsResult[Resident] = (json \ "residents")(1).validate[Resident]
// JsSuccess(Resident(Bigwig,6,Some(Owsla)),)

Next: JSON with HTTP