Play! Framework Json Example
As we’ve seen in our previous example, Play! is a powerful web mvc framework.
Among other stuff, Play has support for rest clients (in its WS package), web sockets, and our subject today: json.
In this example, we’ll see how to write a Restful Web Service with Play! Framework with both scala and java languages using java 1.8.0_66, scala 2.11.6 and Play 2.4.3.
1. JSON
JSON (JavaScript Object Notation) is defined by wikipedia as “an open standard format that uses human-readable text to transmit data objects consisting of attribute–value pairs”.
In other words, we can see JSON as a key-value map, between curly braces, with each entry separated by commas, like:
{ "some_string": "my awesome string", "array_key": [ "my awesome first array item", "my awesome second array item" ], "some_boolean": true, "some_number": 1234 }
Given this first approach, let’s get dirty and write some code. Have a look at this example of how to get started with play, and then come back to write a rest API on top of it.
2. The Code
So, now we have a play application, either on scala or java, up and running. And now we’ll write some actual code.
Let’s make our model, in a package model
next to controllers
, and write a class Person
, which will be our domain.
Person.java
package model; public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } }
Person.scala
package model; case class Person(name: String, age: Int)
As you might be guessing, a JSON representation of a Person such as Person("michael", 21)
would look like:
{ "name": "michael", "age": 21 }
So how do we receive and send something like this in a Play Action?
Let’s move back to our controllers
package and write a PersonController
PersonController.java
package controllers; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import play.*; import play.libs.Json; import play.mvc.*; import model.Person; import java.util.LinkedList; import java.util.List; public class PersonController extends Controller { private final List repository = new LinkedList(); public Result all() { ArrayNode result = Json.newArray(); repository.forEach(p -> { ObjectNode node = Json.newObject(); node.put("name", p.getName()); node.put("age", p.getAge()); result.add(node); }); return ok(result); } @BodyParser.Of(BodyParser.Json.class) public Result insert() { JsonNode json = request().body().asJson(); String name = json.findPath("name").textValue(); int age = json.findPath("age").intValue(); repository.add(new Person(name, age)); return ok(); } }
PersonController.scala
package controllers import model.Person import play.api.libs.functional.syntax._ import play.api.libs.json._ import play.api.mvc._ import scala.collection.mutable class PersonController extends Controller { val repository: mutable.MutableList[Person] = new mutable.MutableList[Person]() implicit val personWrites: Writes[Person] = ( (JsPath \ "name").write[String] and (JsPath \ "age").write[Int] )(unlift(Person.unapply)) implicit val personReads: Reads[Person] = ( (JsPath \ "name").read[String] and (JsPath \ "age").read[Int] )(Person.apply _) def all = Action { Ok(Json.toJson(repository)).as(JSON) } def insert = Action(BodyParsers.parse.json) { request => val either = request.body.validate[Person] either.fold( errors => BadRequest("invalid json person"), person => { repository.+=(person) Ok } ) } }
Here, we are defining an array of people, an action to insert a person in this array, an another one to retrieve them all.
The java controller’s json mapping is pretty straight forward. Play uses fasterxml and you can use ObjectNode as a Map implementation, and ArrayNode as a collection implementation (-ish). So there isn’t much to say about it.
The magic is going on in the scala side this time, play makes it more transparent with its Writes/Reads implementations. I defined two implicits: PersonWrites
and PersonReads
. The Writes defines a procedure to convert from the unapply
method to JSON, and the Reads defines another one to convert from a JSON to the list of parameters of the apply
method.
Now, if you check the signature of validate[-A]
in the first line of our insert
action, you’ll see it receives and implicit Reads. The same goes to Json.toJson
, which receives an implicit Writes as parameter.
So now, we route them in our routes
file in the conf
directory:
routes
# Routes # This file defines all application routes (Higher priority routes first) # ~~~~ # Home page GET / controllers.Application.index() POST /people controllers.PersonController.insert() GET /people controllers.PersonController.all() # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
3. The Test
Now we run our application and run this command in our favorite console:
curl -v --request POST --header "Content-Type: application/json" --data '{ "name": "Michael", "age": 21 }' http://localhost:9000/people
This should output some text in which should be the http response code (which should be 200). And then we run:
curl http://localhost:9000/people
And the output should look like:
[{"name":"Michael","age":21}]
4. Wrapping this up
Of course there are other ways to do this kind of things. Play’s JSON support is not the only way to provide Restful web services in a Play application, but this is a basic approach making use of it, and as you can see, it’s pretty straight forward, readable and effective.
5. Download the Code Project
This was a tutorial on one of the many many many many many ways to write a Restful web service in a play application.
You can download the full source code of this example here: play-scala-example, play-java-example
I am getting an error on the lines;
node.put(“name”, p.getName());
node.put(“age”, p.getAge());
The error is cannot resolve method.
What might be causing the issue?