Table of contents
1.
🙋Introduction
2.
JSON Basics🤓
2.1.
The Play JSON Library 
2.2.
Conversion to JsValue
2.3.
Traversing a JsValue structure
2.4.
Conversion From JsValue 
3.
JSON with HTTP🤓
3.1.
Serving a List of Entities in JSON
3.2.
Creating a New Entity Instance in JSON
4.
JSON Reads/Writes/Format Combinators🤓
4.1.
JsPath
4.2.
Reads
4.3.
Validation with Reads
4.4.
Writes
4.5.
Format
5.
JSON Automated Mapping🤓
6.
JSON Transformers🤓
6.1.
JSON transformers- Reads[T <: JsValue]
7.
Frequently asked questions
7.1.
What are some of the valid JSON types?
7.2.
Give some examples of Reads validation helpers.
7.3.
What is the default validation for Reads?
7.4.
What are the requirements for classes for macros to work?
8.
Conclusion
Last Updated: Mar 27, 2024
Medium

Scala developers-Working with JSON

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

🙋Introduction

This article will take us through JSON basics, the Play JSON library, JSON Reads/Writes/Format Combinators, JSON automated mapping, and JSON transformers in ScalaJava developers and Scala developers use Play for developing web applications. It unifies the API and components needed for developing a web application. 

introduction

Let us start with understanding JSON basics in Scala. 

JSON Basics🤓

JSON (JavaScript Object Notation) data is often parsed and generated in modern online applications. Play has a JSON library that supports it. JSON is a simple data-interchange format. Look at this first code snippet to see how JSON data looks like:

{
  "firm" : "Coding Ninjas",
  "location" : {
    "lat" : 28.4595,
    "long" : 77.0266
  },
  "employees" : [ {
    "name" : "ABC",
    "age" : 18,
    "position" : null
  }, {
    "name" : "XYZ",
    "age" : 22,
    "position" : "Intern"
  } ]
}

The Play JSON Library 

To use JSON data, Play has a package called play.api.libs.json. The play.api.libs.json package provides JSON data representation data structures and utilities for converting between these data structures and other data representations. 

Play JSON Library

This package includes the following features:
 

  1. Automatic conversion to and from case classes is possible.
  2. It supports custom validation while parsing.
  3. It is highly customizable.

 

The package provides three types of data :

  1. JsValue: This can represent any JSON value. We may create a representation of any JSON structure using the different JsValue types.
  2. Json: It is an object used for converting from JsValue structures.
  3. JsPath: This helps traverse the patterns for implicit conversions and JsValue structures.
     

Now let us see all the ways we can convert to a JsValue:

Conversion to JsValue

1. Using String Parsing:

import play.api.libs.json._
val json: JsValue = Json.parse("""
  {
   "firm" : "Coding Ninjas",
  "location" : {
    "lat" : 28.4595,
    "long" : 77.0266
  },
  "employees" : [ {
    "name" : "ABC",
    "age" : 18,
    "position" : null
  }, {
    "name" : "XYZ",
    "age" : 22,
    "position" : "Intern"
  } ]
  }
  """)

 

2. Using Class Construction

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(
  "firm"     -> "Coding Ninjas",
  "location" -> Json.obj("lat" -> 28.4595, "long" -> 77.0266),
  "employees" -> Json.arr(
    Json.obj(
      "name" -> "ABC",
      "age"  -> 18,
      "position" -> JsNull
    ),
    Json.obj(
      "name" -> "XYZ",
      "age"  -> 22,
      "position" -> "Intern"
    )
  )
)

 

3. Using Writes Converters

The utility method Json.toJson(T) helps convert Scala to JsValue. Implicit Writes is available in the Play JSON API for basic types like int, double, boolean, and string. E.g.:

import play.api.libs.json._

val Name  = Json.toJson("ABC")
val Age  = Json.toJson(18)
val Bool= Json.toJson(false)

// collections of basic types
val i_array    = Json.toJson(Seq(18,22))
val s_array = Json.toJson(List("ABC", "XYZ"))

 

For converting our models to JsValues, we must define implicit Writes converters and then provide them in scope. For eg, for location model:

case class Location(lat: Double, long: Double)

 

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

 

Now let us discuss how we can traverse a JsValue structure and then proceed to see how we can convert from JsValue.

Traversing a JsValue structure

Using the \ operator, we can obtain the value of the argument specified in the JsObject.

val emp_one = (json \ "employees" \ 1).get
// returns {"name":"XYZ","age":22,"role":"Intern"}

 

Using the \\ operator, we can obtain the value of the specified JsObject and all its descendants.

Now let us proceed to see how we can convert from JsValue.

Conversion From JsValue 

1. Using string utilities 

a. Minified

val minifiedString: String = Json.stringify(json)

 

b. Readable

val readableString: String = Json.prettyPrint(json)

 

2. Using JsValue.as/asOpt

- using JsValue.as[T]

val firm = (json \ "firm").as[String]
// "Coding Ninjas"

val names = (json \\ "name").map(_.as[String])
// Seq("ABC", "XYZ")

 

This method JsValue.as[T] throws an exception if a conversion is impossible or the specified path is unavailable. To overcome this we use another method JsValue.asOpt[T].

-using JsValue.asOpt[T]

val firm = (json \ "firm").asOpt[String]
// Some("Coding Ninjas")

 

3. JsValue to a model

To convert from JsValue to the model, we need to implicitly define Reads[T]

case class Location(lat: Double, long: Double)

 

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

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

 

Play supports HTTP requests and responses with JSON content by combining the HTTP API with the JSON library. We'll walk through the principles by creating a small RESTful web service. For all data, the service will use the JSON content type.

JSON with HTTP🤓

Let us consider the example to understand this:

JSON with HTTP
case class information(age: Int, salary: Int)
case class Employee(name: String, info: information)
object Employee {
  var list: List[Employee] = {
    List(
      Employee(
        "ABC",
        information(18,0)
      ),
      Employee(
        "XYZ",
        information(22, 5000)
      )
    )
  }

  def save(employee: Employee) = {
    list = list ::: List(employee)
  }
}

 

Before we start, we need to add a list of entities in JSON. First, we add the necessary imports to our controller.

Serving a List of Entities in JSON

import play.api.mvc._
class HomeController @Inject() (cc: ControllerComponents) extends AbstractController(cc) {}

 

Now we need to convert our model to a JsValue representation. So for this, we will define Writes[Employee].

implicit val infoWrites: Writes[information] = (JsPath \ "age").write[Int].and((JsPath \ "salary").write[Int])(unlift(information.unapply))

implicit val employeeWrites: Writes[Employee] = (JsPath \ "name").write[String].and((JsPath \ "info").write[information])(unlift(Employee.unapply))

 

Now that Writes[Employee] is defined, we can define Action.

def listEmployees = Action {
  val json = Json.toJson(Employee.list)
  Ok(json)
}

 

The Action we have defined above will obtain a list of Employee objects. It will then convert the received information into JsValue using Json.toJson and return it as a result. 

Creating a New Entity Instance in JSON

We will start by defining a Reads[Employee] to convert a JsValue to a model for creating a new entity instance in JSON.

implicit val infoReads: Reads[information] = (JsPath \ "age").read[Int].and((JsPath \ "salary").read[Int])(information.apply _)

implicit val employeeReads: Reads[Employee] = (JsPath \ "name").read[String].and((JsPath \ "info").read[information])(Employee.apply _)

 

Now the Action will be: 

def saveEmployee = Action(parse.json) { request =>
  val employeeResult = request.body.validate[Employee]
  employeeResult.fold(
    errors => {
      BadRequest(Json.obj("message" -> JsError.toJson(errors)))
    },
    employee => {
      Employee.save(employee)
      Ok(Json.obj("message" -> ("Employee '" + employee.name + "' saved.")))
    }
  )
}

 

Let us understand this:

  • This Action anticipates a request with a Content-Type header and a body providing a JSON representation of the object it has to create.
  • It will parse the request and will provide the request.body as JsValue.
  • For conversion, we use the validate function, which will rely on our implicit Reads[Employee].
  • We utilized a fold with error and success flows to process the validation result. 
  • The Action sends JSON responses.

 

Finally, we need to add a route binding in conf/routes:

POST  /employees               controllers.Application.saveEmployee

 

Now that we have an idea of how Play works with JSON, we can see that much work is done in defining Reads and Writes. Let us now discuss these in detail.

JSON Reads/Writes/Format Combinators🤓

Reads and Writes help us to convert JsValue structures and other values. In this section, we will refer to the first code snippet used in this article.

JSON Reads/Writes/Format Combinators

JsPath

Using JsPath, we can find the location of data in a JsValue structure.

// Simple path
val l = JsPath \ "location" \ "lat"

// Recursive path
val n = JsPath \\ "name"

Reads

Reads convert from JsValue to another value. To create complex Reads, we can nest them. 

The libraries that we need are:

import play.api.libs.json._       // JSON library
import play.api.libs.json.Reads._ // Custom validation helpers

 

Individual path Reads-

val n: Reads[String] = (JsPath \ "name").read[String]

 

Complex Reads-

Individual paths combined make complex Reads.

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

 

In the above lines of code, we combine the individual paths using the and combinator to make complex Reads.

Then we use the apply method to translate the individual values to our model.

Validation with Reads

The JsValue.validate method is helpful in validating a conversion from JsValue to another type.

val json = { ... }

val f: Reads[String] = (JsPath \ "firm").read[String]

val result: JsResult[String] = json.validate[String](f)
result match {
  case s: JsSuccess[String] => println("Firm Name: " + s.get)
  case e: JsError           => println("Errors: " + JsError.toJson(e).toString())
}

Writes

Using the Writes[T] converter, we change some other types to JsValue. Using Writes is similar to Reads. Let us see the Writes for our example:

case class Location(lat: Double, long: Double)
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))

Format

Format[T] is the combination of Reads and Writes.

val l_R: Reads[Location] = (
  (JsPath \ "lat").read[Double](min(-90.0).keepAnd(max(90.0))) and
    (JsPath \ "long").read[Double](min(-180.0).keepAnd(max(180.0)))
)(Location.apply _)

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

implicit val locationFormat: Format[Location] = Format(l_R, l_W)

JSON Automated Mapping🤓

Because JSON can directly map to a class, we create a macro so that we do not have to write the Writes[T], Reads[T] or Format[T] manually. Let us see how it is done:

JSON Automated Mapping
case class Employees(name: String, age: Int, position: Option[String]) //this is the case class

 

Now let us write a macro that will create a Reads[Employees] :

import play.api.libs.json._
implicit val employeeReads = Json.reads[Employees]

 

After compiling, it will inject the same code for Reads as we would write manually. Similar macros can be written for Writes or Format.

JSON Transformers🤓

How do we handle a case where we need to read from JSON, then validate it and then convert it back to JSON? To address this, we have a set of combinators and an API called JSON Transformers.

JSON Transformers

JSON transformers- Reads[T <: JsValue]

Reads[T<:JsValue] can also transform, not only validate or read.

Let us understand with a code. The following JSON code is used for a demo of JSON Transformers.

{
  "key1" : 1,
  "key2" : {
    "key21" : [ "value1", "value2"]
  },
  "key3" : "value3"
}

 

We can use the transformer with various methods of obtaining a JsValue. Let us see all of them:

1. Let us pick up value as JsValue:

import play.api.libs.json._

val jsonTransformer = (__ \ 'key2 \ 'key21).json.pick

scala> json.transform(jsonTransformer)
res9: play.api.libs.json.JsResult[play.api.libs.json.JsValue] = 
  JsSuccess(
    ["value1","value2"],
    /key2/key21
  )

 

Explanation:

(__ \ 'key2 \ 'key21).json.pick

pick is a Reads[JsValue] which obtains the value in the given JsPath (__)

 

JsSuccess(["value1","value2"],/key2/key21)

This is a simply successful JsResult.
 

2. Pick branch as JsValue

import play.api.libs.json._

val jsonTransformer = (__ \ 'key2 \ 'key21).json.pickBranch

scala> json.transform(jsonTransformer)
res11: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = 
  JsSuccess(
  {
    "key2": {
      "key21":["value1", "value2"]
     }
  },
  /key2/key21
  )

 

Explanation:

(__ \ 'key2 \ 'key21).json.pickBranch

pickBranch is also a Reads[JsValue] which obtains the branch from root to given JsPath.

 

3. Copy a value from input JsPath into a new JsPath

import play.api.libs.json._

val jsonTransformer = (__ \ 'key22 \ 'key221).json.copyFrom( (__ \ 'key2 \ 'key21).json.pick )

scala> json.transform(jsonTransformer)
res12: play.api.libs.json.JsResult[play.api.libs.json.JsObject] 
  JsSuccess( 
    {
      "key22":{
        "key221":["value1","value2"]
      }
    },
    /key2/key21
  )

 

Explanation: 

(__ \ 'key22 \ 'key221).json.copyFrom( reads: Reads[A <: JsValue )

copyFrom obtains the value (JsValue) from input JSON and copies the obtained value to the given JsPath.
 

4. Copy full input Json & update a branch

We can add a new path using (JsPath).json.put( a: => JsValue ). It replaces the JSON value by a.

Frequently asked questions

What are some of the valid JSON types?

Some of the valid JSON types are JsString, JsNumber, JsBoolean, JsObject, JsArray and JsNull. 

Give some examples of Reads validation helpers.

Reads.email to validate an email, Reads.minLength(any_string) to validate the minimum length of a string, Reads.min to validate the minimum value, and Reads.max to validate maximum value are some of the Reads validation helpers.

What is the default validation for Reads?

The default validation for Reads is checking for type conversion errors.

What are the requirements for classes for macros to work?

The unapply method's return types must match the apply method's argument types.

The apply method's argument names must match the property names specified in the JSON.

These needs are met automatically by case classes. 

Conclusion

In this article, we learned JSON basics and the JSON Play library. We also saw how we could convert JsValue and other types into each other. Then we discussed how JSON works with HTTP, JSON Reads/Write/Format. This article also throws some light on JSON automated mapping and JSON transformers. It was a summary of how Scala developers use JSON in Play.

Also keep learning DSA and web technologies on our website. Study core subjects like DBMSSoftware engineering, and computer networks. Do not stop here! Expand your knowledge base by learning machine learningdeep learningcomputer vision, and big data. Also, you can always try our paid courses to upskill yourself. Happy Learning!

Live masterclass