Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a generic Json serialization function

Is it possible to create a generic function in Scala, using Play Framework 2.2, that will serialize an arbitrary object to JSON, without having to be supplied a writer or formatter?

For instance, this non-generic code will create a JSON response given a Customer:

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

case class Customer(id: Int, name: String)

object scratch {
  val p = Customer(1, "n")                        
  //> p  : Customer = Customer(1,n)

  def createJsonResponseCustomer(data: Customer) = {
    implicit val formatter = Json.format[Customer]
    Json.obj("success" -> true, "data" -> Json.toJson[Customer](data))
  }

  createJsonResponseCustomer(p)                   
  //> res0: play.api.libs.json.JsObject = {"success":true,"data":{"id":1,"name":"n"}}
}

To avoid having to define the formatter for each different object, I'd like to create a generic function like this:

def createJsonResponse[T](data: T) = {
  implicit val formatter = Json.format[T]
  Json.obj("success" -> true, "data" -> Json.toJson[T](data))
}

But this attempt produces the error No unapply function found at Json.format[T].

In other words, this works:

def getFormatter(c: Customer) = Json.format[Customer]

but this doesn't:

def getFormatterGeneric[T](c: T) = Json.format[T]

Is there any way around this?

like image 316
Fernando Correia Avatar asked Oct 26 '13 20:10

Fernando Correia


1 Answers

You need to define the formatter somewhere, for each type you wish to read or write. This is because the formatter instances are resolved at compile time, not at runtime. This is a good thing, because it means trying to serialize a type that does not have a serializer becomes a compile-time error, not a runtime one.

Instead of defining the formatters on the fly, define them in a module that you can reuse, e.g.

object JsonFormatters {
  implicit val customerWrites: Format[Customer] = Json.format[Customer]
}

Then import JsonFormatters._ in the scope that you want to write some JSON.

Now, you can write a generic method similar to what you wanted: you just have to specify the requirement for a formatter in the signature of your method. In practice, this is an implicit paramter of type Writes[T].

def createJsonResponse[T](data: T)(implicit writes: Writes[T]) =
  Json.obj("success" -> true, "data" -> Json.toJson[T](data))

You can also write this method signature using context bound syntax, i.e.

def createJsonResponse[T : Writes](data: T) = ...

This requires that there is an instance of Writes[T] in scope; but the compiler will choose the correct instance for you based on the type T, rather than you resolving it explicitly.

Note that Writes[T] is a supertype of Format[T]; since you are only writing JSON in this method, there's no need to specify a requirement for Format[T], which would also give you Reads[T].

like image 157
Ben James Avatar answered Oct 11 '22 05:10

Ben James