Spray JSON offers a neat and simple way of writing Marshallers and Unmarshallers for the Scala case classes and objects. It is so simple, it might not need any further explanation. The documentation here is self explanatory.

Here's a simple example of creating Marshallers and Unmarshallers for a simple case class in Spray JSON.

  case class Employee(name: String, employed: Boolean)
  import spray.json._
  import DefaultJsonProtocol._
  implicit val employeeJsonFormat = jsonFormat2(Employee)
  println(Employee("Lola", true).toJson)
  println("""{"name": "LolaYeh", "employed":true}""".parseJson.convertTo[Employee])

The statements implicit val employeeJsonFormat = jsonFormat2(Employee) defines a marshaller and un-marshaller for the Employee case class. Since, the employee case class is a case class with 2 members, we use jsonFormat2 method. Since the members have primitive data types String and Boolean, we simply import DefaultJsonProtocol._ to import string primitive marshallers and un-marshallers.

The toJson method on the employee class becomes available by the spray.json._ implicit imports. That's pretty cool, huh.

Using Optional Values with Spray JSON

Now what we did above is pretty cool and simple. Spray JSON bundles tons of other features. Let's say we wanted to make sure that the date of birth is an optional member.

Well, the solution is quite simple again.

  case class Employee(name: String, employed: Option[Boolean])
  import spray.json._
  import DefaultJsonProtocol._
  implicit val employeeJsonFormat = jsonFormat2(Employee)
  println(Employee("Lola", Some(true)).toJson)
  println("""{"name": "LolaYeh"}""".parseJson.convertTo[Employee])

What we did above is only changed the member type of employed, since the DefaultJsonProtocol._ defines implicit protocol for optional fields as well, we are good to go.

Using default values with the Spray JSON

The above marshaller and un-marshaller works well for the Option data values where a field may be available or not and we map it to Some or None. However, in some cases, we want the fields to be mapped to a default value, especially in cases like the HTTP endpoint for REST API's.

What we need is custom marshallers and un-marshallers for the same. Well, with Spray JSON that is also a piece of cake.

  case class Employee(name: String, employed: Boolean)

  import spray.json._
  import DefaultJsonProtocol._

  implicit object EmployeeJSONFormat extends RootJsonFormat[Employee] {
    override def write(obj: Employee): JsValue = JsObject(
      "name" -> JsString(obj.name),
      "employed" -> JsBoolean(obj.employed)
    )


    override def read(json: JsValue): Employee = {
      val fields = json.asJsObject("{Invalid JSON Object").fields
      Employee(
        fields.get("string").fold("Lola")(_.convertTo[String]),
        fields.get("employed").fold(false)(_.convertTo[Boolean])
      )
    }
  }

  println(Employee("Lola", true).toJson)
  println("""{"name": "LolaYeh"}""".parseJson.convertTo[Employee])

In the snippet above, we have created an Employee case class without optional fields which we want to map to the default values if it's not available.

We further created custom JsonFormat for the case class Employee with a method for marshalling write(...) and for un-marshalling read(...).

The marshaller converts the given Scala class object to the respect Spray JSON AST which can be used by the Spray JSON utilities to write to various output streams. As you can see in the example below, we created an object consisting of key-value pairs of JSON key name and the respective String/Boolean/Other values.

    override def write(obj: Employee): JsValue = JsObject(
      "name" -> JsString(obj.name),
      "employed" -> JsBoolean(obj.employed)
    )

The un-marshaller converts the given Spray JSON AST to the respective case class. As in the snippet below, we convert the JSON AST to object, since we know it's a complex JSON type and not a single primitive. Further, we extract the fields viz. (key, value) pairs from the object and map each to the Employee case class if they exist. If the JSON AST object does not contain the respective fields, we provide a default value using the fold method.

    override def read(json: JsValue): Employee = {
      val fields = json.asJsObject("{Invalid JSON Object").fields
      Employee(
        fields.get("string").fold("Lola")(_.convertTo[String]),
        fields.get("employed").fold(false)(_.convertTo[Boolean])
      )
    }

Well, that's it for now. Spray JSON is a really cool and simple library to provide JSON support in the native applications as well as integrated support with frameworks like Akka, Play, and Lagom.

Thanks.