Documentation

The Play JSON library Basics

Overview

The recommended way of dealing with JSON is using Play’s typeclass based JSON library, located at play.api.libs.json.

For parsing JSON strings, Play uses super-fast Java based JSON library, Jackson.

The benefit of this approach is that both the Java and the Scala side of Play can share the same underlying library (Jackson), while Scala users can enjoy the extra type safety and functional aspects that Play’s JSON support brings to the table.

JSON is an AST (_Abstract Syntax Tree_)

Take JSON example:

{ 
  "user": {
    "name" : "toto",
    "age" : 25,
    "email" : "toto@jmail.com",
    "isAlive" : true,
    "friend" : {
  	  "name" : "tata",
  	  "age" : 20,
  	  "email" : "tata@coldmail.com"
    }
  } 
}

This can be seen as a tree structure using the 2 following structures:

If you want to have more info about the exact JSON standard, please go to json.org

Json Data Types

play.api.libs.json package contains 7 JSON data types reflecting exactly the previous structure.

JsObject

JsNull

This represents null value in JSON

JsBoolean

This is a boolean with value true or false

JsNumber

JsArray

JsString

A classic String.

JsUndefined

This is not part of the JSON standard and is only used internally by the API to represent some error nodes in the AST.

JsValue

All previous types inherit from the generic JSON trait, JsValue.

Minimal Import to work with basic JSON API

import play.api.libs.json.Json

This import give access to the most basic JSON features :

Parsing a JSON String

You can easily parse any JSON string as a JsValue:

import play.api.libs.json.Json

val json: JsValue = Json.parse("""
{ 
  "user": {
    "name" : "toto",
    "age" : 25,
    "email" : "toto@jmail.com",
    "isAlive" : true,
    "friend" : {
  	  "name" : "tata",
  	  "age" : 20,
  	  "email" : "tata@coldmail.com"
    }
  } 
}
""")

This sample is used in all next samples.

As explained previously, the parsing is performed by Jackson.

Constructing JSON directly

Raw way

The previous sample Json object can be created in other ways too.
Here is the raw approach.

import play.api.libs.json._

JsObject(
  "users" -> JsArray(
    JsObject(
      "name" -> JsString("Bob") ::
      "age" -> JsNumber(31) ::
      "email" -> JsString("bob@gmail.com") ::
      Nil) ::
    JsObject(
      "name" -> JsString("Kiki") ::
      "age" -> JsNumber(25) ::
      "email" -> JsNull ::
      Nil
    ) :: Nil
  ) :: Nil
)

Preferred way

Play now provides a simplified syntax to build your JSON.
The previous JsObject can be constructed as following:

import play.api.libs.json.Json

Json.obj(
  "users" -> Json.arr(
    Json.obj(
      "name" -> "bob",
      "age" -> 31,
      "email" -> "bob@gmail.com"  	  
    ),
    Json.obj(
      "name" -> "kiki",
      "age" -> 25,
      "email" -> JsNull  	  
    )
  )
)

Serializing JSON

Serializing a JsValue to its JSON String representation is easy:

import play.api.libs.json.Json

val jsonString: String = Json.stringify(jsValue)

Accessing Path in a JSON tree

As soon as you have a JsValue you can navigate into the JSON tree.
The API looks like the one provided to navigate into XML document by Scala using NodeSeq except you retrieve JsValue.

Simple path \

// Here we import everything under json in case we need to manipulate different Json types
scala> import play.api.libs.json._

scala> val name: JsValue = json \ "user" \ "name"
name: play.api.libs.json.JsValue = "toto"

Recursive path \\

// recursively searches in the sub-tree and returns a Seq[JsValue]
// of all found JsValue
scala> val emails: Seq[String] = json \ "user" \\ "email"
emails: Seq[play.api.libs.json.JsValue] = List("toto@jmail.com", "tata@coldmail.com")

Converting JsValue to Scala Value

While navigating JSON tree, you retrieve JsValue but you may want to convert the JsValue to a Scala type.
For ex, a JsString to a String or a JsNumber to a Long (if it can be converted).

Unsafe conversion with json.as[T]

as[T] is unsafe because it tries to access the path and to convert to the required type. But if the path is not found or the conversion not possible, it generates a JsResultException RuntimeException containing detected errors.

case OK: path found & conversion possible

// returns the value converted to provided type (if possible and if found)
scala> val name: String = (json \ "user" \ "name").as[String]
name: String = toto

case KO: Path not found

scala> val nameXXX: String = (json \ "user" \ "nameXXX").as[String]
play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(validate.error.expected.jsstring,WrappedArray())))))
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsResult$class.fold(JsResult.scala:69)
	at play.api.libs.json.JsError.fold(JsResult.scala:10)
	at play.api.libs.json.JsValue$class.as(JsValue.scala:63)
	at play.api.libs.json.JsUndefined.as(JsValue.scala:96)

Please note the error that doesn’t return path.not.found as you may expect. This is a difference from JSON combinators presented later in the doc.
This is due to the fact that (json \ "user" \ "nameXXX") returns JsNull and the implicit Reads[String] here awaits a JsString which explains the detected error.

case KO: Conversion not possible

scala> val name: Long = (json \ "user" \ "name").as[Long]
play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(validate.error.expected.jsnumber,WrappedArray())))))
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsValue$$anonfun$4.apply(JsValue.scala:65)
	at play.api.libs.json.JsResult$class.fold(JsResult.scala:69)
	at play.api.libs.json.JsError.fold(JsResult.scala:10)
	at play.api.libs.json.JsValue$class.as(JsValue.scala:63)
	at play.api.libs.json.JsString.as(JsValue.scala:111)


Safer conversion with Option[T]

as[T] is immediate but not robust so there is asOpt[T] which returns None in case of error of any type.

case OK: path found & conversion possible

scala> val maybeName: Option[String] = (json \ "user" \ "name").asOpt[String]
maybeName: Option[String] = Some(toto)

case KO: Path not found

scala> val maybeNameXXX: Option[String] = (json \ "user" \ "nameXXX").asOpt[String]
maybeNameXXX: Option[String] = None

case KO: Conversion not possible

scala> val maybeNameLong: Option[Long] = (json \ "user" \ "name").asOpt[Long]
maybeNameLong: Option[Long] = None


Safest conversion with validate[T]

asOpt[T] is better but you lose the kind of error that was detected.

validate[T] is there to provide the safest and most robust way to convert a JsValue by returning a JsResult[T]:

JsResult[T] in a very nutshell

JsResult[T] can have 2 values:

Please note : JsPath will be described later but it is just the same as XMLPath for JSON.
When you write :
json \ "user" \ "name"
It can be written as following :
(JsPath \ "user" \ "name")(json)
You create a JsPath to search user then name and apply it to a given json.

A few samples of usage:

scala> import play.api.libs.json._

scala> val jsres: JsResult[String] = JsString("toto").validate[String]
jsres: JsSuccess("toto")

scala> val jsres: JsResult[String] = JsNumber(123).validate[String]
jsres: play.api.libs.json.JsResult[String] = JsError(List((,List(ValidationError(validate.error.expected.jsstring,WrappedArray())))))

jsres.map{ s: String => …}
jsres.flatMap{ s: String => JsSuccess(s) }

jsres.fold( 
  errors: Seq[(JsPath, Seq[ValidationError])] => // manage errors,
  s: String => // manage value 
)

jsres.map( s: String => // manage value )
     .recoverTotal( jserror: JsError => // manage errors and return default value)

case OK: path found & conversion possible

scala> val safeName = (json \ "user" \ "name").validate[String]
safeName: play.api.libs.json.JsResult[String] = JsSuccess(toto,) // path is not precised because it's root

case KO: Path not found

scala> val nameXXX = (json \ "user" \ "nameXXX").validate[String]
nameXXX: play.api.libs.json.JsResult[String] = 
  JsError(List((,List(ValidationError(validate.error.expected.jsstring,WrappedArray())))))

Please note the error that doesn’t return path.not.found as you may expect. This is a difference from JSON combinators presented later in the doc.
This is due to the fact that (json \ "user" \ "nameXXX") returns JsNull and the implicit Reads[String] here awaits a JsString which explains the detected error.

case KO: Conversion not possible

scala> val name = (json \ "user" \ "name").validate[Long]
name: play.api.libs.json.JsResult[Long] = 
  JsError(List((,List(ValidationError(validate.error.expected.jsnumber,WrappedArray())))))


Converting Recursive path \\

\\ recursively searches in the sub-tree and returns a Seq[JsValue] of found JsValue which is then a collection with classical Scala functions.

scala> val emails: Seq[String] = (json \ "user" \\ "email").map(_.as[String])
emails: Seq[String] = List(toto@jmail.com, tata@coldmail.com)


Converting a Scala value to JsValue

Scala to JSON conversion is performed by function Json.toJson[T](implicit writes: Writes[T]) based on implicit typeclass Writes[T] which is just able to convert a T to a JsValue.

Create very simple JsValue

val jsonNumber = Json.toJson(4)
jsonNumber: play.api.libs.json.JsValue = 4

This conversion is possible because Play JSON API provides an implicit Writes[Int]

Create a JSON array from a Seq[T]

import play.api.libs.json.Json

val jsonArray = Json.toJson(Seq(1, 2, 3, 4))
jsonArray: play.api.libs.json.JsValue = [1,2,3,4]

This conversion is possible because Play JSON API provides an implicit Writes[Seq[Int]]

Here we have no problem to convert a Seq[Int] into a Json array. However it is more complicated if the Seq contains heterogeneous values:

import play.api.libs.json.Json

val jsonArray = Json.toJson(Seq(1, "Bob", 3, 4))
<console>:11: error: No Json deserializer found for type Seq[Any]. Try to implement an implicit Writes or Format for this type.
       val jsonArray = Json.toJson(Seq(1, "Bob", 3, 4))

You get an error because there is no way to convert a Seq[Any] to Json (Any could be anything including something not supported by Json right?)

A simple solution is to handle it as a Seq[JsValue]:

import play.api.libs.json.Json

val jsonArray = Json.toJson(Seq(
  toJson(1), toJson("Bob"), toJson(3), toJson(4)
))

This conversion is possible because Play API JSON provides an implicit Writes[Seq[JsValue]]

Create a JSON object from a Map[String, T]

import play.api.libs.json.Json

val jsonObject = Json.toJson(
  Map(
    "users" -> Seq(
      toJson(
        Map(
          "name" -> toJson("Bob"),
          "age" -> toJson(31),
          "email" -> toJson("bob@gmail.com")
        )
      ),
      toJson(
        Map(
          "name" -> toJson("Kiki"),
          "age" -> toJson(25),
          "email" -> JsNull
        )
      )
    )
  )
)

That will generate this Json result:

{
  "users":[
    {
      "name": "Bob",
      "age": 31.0,
      "email": "bob@gmail.com"
    },
    {
      "name": "Kiki",
      "age":  25.0,
      "email": null
    }
  ]
}

Next: JSON Reads/Writes/Formats Combinators