Scala Play: Handling JSON

Micah Lewis
4 min readDec 4, 2020

My company had an internal hack-a-thon and the night before the presentations, I was forced to take some JSON from an API and render it on a page. Complete unfamiliarity compounded with a time restraints had me completely unable to do it. So this is me fixing that for next time:

JS Object

There are a lot of ways to handle JSON but if you’re using the Play framework, you can handle it natively. Just import the play.api.libs.json and you gain access to the JsValue trait which has a ton of case classes for nearly any type. Some examples include JsString, JsNumber, JsArray, JsObject, JsNull, etc. there are necessary for converting json to other types. For now just remember these exist.

Getting JSON Data

If you’re making a request to a REST Api, chances are you need to use a WS [Web Socket] request. Once you receive the response, in order it to process it as JSON, just add .json to the response.

val requestUrl= "www.somewebsite.com/theendpoint"ws.url(url).get().map( response =>
response.json
)

Next, lets imagine that your response data was the following JSON. We’ll be using this for our examples here:

{
"customers": [
{
"name": "john",
"age": 45,
"children": ["john jr", "john the 3rd"]
},
{
"name":"micah",
"age": 30
}
]
}

Getting data out of your JSON

You can then process the response to get the specific values out of your json by applying the “ \” operator to them to traverse the JSON object.

val john = (response.json \ "customers").get
//returns JsArray([{"name":john,"age":45,children:["john jr", "john the 3rd"}, {"name":"micah", "age":30}])

You can also process array data by simply using numbers.

val john = (response.json \ "customers" \ 1).get
// returns JsObject({"name":"micah", "age":30})

Note that if you make a mistake in your .get request here, you’ll receive an exception. Additionally, rather than traversing the JSON object using the “\” operator, you can use “\\” to search the entire json object and return any value whose key is specified.

val customers = (response.json \\ "name")
// returns List(JsString("john"), JsString("micah"))

Be careful with how powerful this is. If you want to pull “names” of only customers, but your json has two objects, one for customers, and one for companies, both with the key “name”, it will pull both of them. Note that you also do not need to append the .get function at the end for “\\” searches. Also if you search for keys that do not exist in your JSON object, it will return an empty list.

Don’t do this if they are paying you

Much like the “\\” operator we just talked about you can do the following to also search for information with a specific key:

val names = response.json("name")
//returns List(JsString("john"), JsString("micah"))

A FIRM WARNING

  • Do not use this for production code. It is not optimized.
  • Should only use it for REPL and one-off scripts.
  • Only use this for keys you know exist.

Converting from JSON to … well anything

We’ll go down the list of probably least useful but fastest to most useful but longest ways to convert JSON data.

To convert to a string, you can use JSON.stringify(), which will give you some minified [all smashed on one line] version of your JSON. You can also use JSON.prettyPrint() to retain the physical structure of your JSON object.

val minifiedString: String = Json.stringify(json)
val readableString: String = Json.prettyPrint(json)

Let’s imagine you want to convert your JSON to a specific type, like Int, or Long, etc. You can use the .as[T] conversion, where the “T” stands for the type you want to convert to.

val name = (response.json \ "customers" \ 1).as[String]
// returns "micah"

The .as[T] can also be used for multiple values when used in conjunction with .map. You can so something like below:

val names = (response.json \\ "name").map(_.as[String])
// returns List("john", "micah")

Going further down the list, in a more Scala Play-esque way you can also return the JSON information as an Option by simply using .asOpt[T]. Note that by doing this, you will lose any error information as, returning information that does not exist, results in None.

val name = (response.json \ "customers" \ 1 \ "name").asOpt[String]
// returns Some("micah")
val names = (response.json \\ "name").map(_.asOpt[String])
//returns List(Some("john"), Some("micah"))
val nope = (response.json \ "bogus").asOpt[String]
// returns None

While the above way is fine for production, the SUPER DUPER SCALA WAY to unpack the information is by using .validate[T], which will convert your JsValue to a JsSuccess that wraps the result. If the conversion isn’t successful, you’ll receive a JsError, which will wrap your list of errors.

val name: JSResult[String] = (
(response.json \ “customers” \ 1 \ “name”).validate[String]
)
name match {
case JsSuccess(name) => println(name)
case e: JsError => println(e)
}

Finally, let’s talk about converting to models. If you haven’t read my post on case classes, you can check that out for how to create models using case classes with companion objects.

There is a long way and a short way to do this, but for this tutorial we’ll just look at the short way as it will suffice for most cases, which is if you’re trying to map your JSON info to the model in a 1:1 way. Just create your model and then validate the JSON as the model type.

case class Person(name: String, age: Int)object Person{
implicit val personReads = Json.format[Person]
}
val newPerson: JsResult[Person] = (
(response.json \ "customers" \ 1).validate[Person]
)

Final note here, if you are validating a JSON object against a model, and the JSON object has more keys than the ones in the model. The keys in the JSON object will be ignored. In the example above, if I had done the validate to

(response.json \ “customers” \ 0).validate[Person]
// Person("john", 45)

The “children” key/value pair is ignored. Conversely, if your JSON doesn’t have a key that is in your model. You’ll get an error.

If you got all the way through this, thanks for reading. I hope the info is useful. As always, if you have any questions, comments, or saw any errors in this, let me know in the comments below.

--

--