Создать общую функцию сериализации Json

Можно ли создать универсальную функцию в Scala с использованием Play Framework 2.2, которая будет сериализовать произвольный объект в JSON, без необходимости предоставления средства записи или форматирования?

Например, этот неуниверсальный код создаст ответ JSON, заданный 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"}}
}

Чтобы избежать необходимости определять форматтер для каждого отдельного объекта, я хотел бы создать общую функцию, подобную этой:

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

Но эта попытка выдает ошибку No unapply function found в Json.format[T],

Другими словами, это работает:

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

но это не так:

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

Есть ли способ обойти это?

1 ответ

Решение

Вам нужно где-то определить форматтер для каждого типа, который вы хотите прочитать или написать. Это связано с тем, что экземпляры форматера разрешаются во время компиляции, а не во время выполнения. Это хорошо, потому что это означает, что попытка сериализации типа, который не имеет сериализатора, становится ошибкой во время компиляции, а не во время выполнения.

Вместо определения форматеров на лету, определите их в модуле, который вы можете использовать повторно, например

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

затем import JsonFormatters._ в том объеме, который вы хотите написать JSON.

Теперь вы можете написать универсальный метод, аналогичный тому, который вы хотели: вам просто нужно указать требование для форматера в сигнатуре вашего метода. На практике это неявный параметр типа Writes[T],

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

Вы также можете написать подпись этого метода, используя синтаксис с привязкой к контексту, т.е.

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

Это требует, чтобы был экземпляр Writes[T] в рамках; но компилятор выберет правильный экземпляр для вас на основе типа T, а не вы решаете это явно.

Обратите внимание, что Writes[T] это супертип Format[T]; поскольку вы пишете только JSON в этом методе, нет необходимости указывать требование для Format[T], что также даст вам Reads[T],

Другие вопросы по тегам