Play 2 Framework with Scala: Complex JSON Validator Examples

Play 2 Framework with Scala: Complex JSON Validator Examples

Last updated:
Play 2 Framework with Scala: Complex JSON Validator Examples
Source

Json is, for many, the lingua franca of the web. Lots of web services and server backends now expect requests to be formatted as json. But being a valid json value does not mean the data is semantically correct.

If a service you are writing is expecting a json object that represents a User and you receive a json object like { "foo": "bar" }, you have bad data even if it's valid json.

Play provides ways to validate json data against a case class

Play provides some ways to validate json data against a case class. Look at the following examples:

Use cases

Defining precisely what shape (attributes) a JSON object must have in order to be considered valid.

See also my other post with Fully Customized Json Validators for Play 2

For example, when defining a controller action that is responsible for persisting data in a database, you want to make sure incoming data is well-formed in order to prevent consistency errors.

Example

First of all you need to define what your data should look like. In this example, we have a simple User model with a required username and an optional email attribute:

package models

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

// define what attributes your model has
case class User(username: String,
                email: Option[String])

// the converters should be placed in a 
// companion object
object User {
  // this informs Play how to convert a json value
  // into a User object
  implicit val reads: Reads[User] = (
    (JsPath \ "username").read[String] and
    (JsPath \ "email").readNullable[String])(User.apply _)
}

Now, in your controller action, you can write a simple piece of code that decides whether the request received is a valid User (using the previously created case class):

package controllers

import play.api.mvc._
import play.api.libs.json._
import models.User

def myAction() = Action { implicit request =>
    // this will fail if the request body is not a valid json value
    val bodyAsJson = request.body.asJson.get

    bodyAsJson.validate[User] match {
        case success: JsSuccess[User] => {
            val username = success.get.username
            Ok("Validation passed! Username is "+username)
        }
        case JsError(error) => BadRequest("Validation failed!")
    }
}

Custom Validators

You will probably also need to define extra validators for your data.

For instance, say you want to validate that the received json value (in addition to matching the case class) has a username where every character is a lowercase letter?

You can't encode that restriction as a type (like a `String), but Play has a few goodies to help us here.

How to require that a user's username contain only lowercase letters?

One way would be to do change the User case class (and companion object) like this:

// extra import needed
import play.api.data.validation.ValidationError

object User {
  // note that this is a val, not a function
  val onlyLowercase: Reads[String] = 
    Reads.StringReads.filter(ValidationError("Invalid username given!"))(str => {
        str.matches("""[a-z]+""")
    })

  // the same as in the previous example
  // but for the extra parameter
  implicit val reads: Reads[User] = (
    (JsPath \ "username").read[String](onlyLowercase) and
    (JsPath \ "email").readNullable[String])(User.apply _)
}

Using Body Parsers

You can tell Play 2 to pre-format your request as a JsValue using a BodyParser, so you don't need to test whether the request body is well-formed json value before actually using it.

So, you could rewrite the previous example as follows:

def myActionWithJsonParser() = Action(BodyParsers.parse.json) { implicit request =>
    // request.body is a JsValue
    // if not a valid json value, Play will throw a 404 BadRequest automatically

   // process the request as per the previous example
}

More info

Dialogue & Discussion