6

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?

1 Answer 1

12

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].

Sign up to request clarification or add additional context in comments.

3 Comments

It might be nice to add that you can just create a Writes[T] automatically: implicit blahWrites = Json.Writes[Blah] (link to docs).
Yes, the reason you can't do it once and for all for all types is because Json.format is a macro which looks at the specific type and generates some type-specific code for it. It obviously can't do that to a type parameter.
Can you please give a full example of doing it in play 2.8? I tested this but no luck :( I want to create a single function to serialize any time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.