Как я могу сделать метод asJson частью класса case с использованием spray-json?

Я хочу иметь возможность создавать классы case в Scala с помощью spray-json, но определить метод asJson в классе, но я не могу понять, как это сделать. Например, я бы хотел сделать это:

case class Foo(bar: String) {
  def toJson: JsValue = ...
}

Было бы достаточно просто создать неявный конвертер JSON:

object JsonProtocols extends DefaultJsonProtocol {
  implicit val fooFormat = jsonFormat1(Foo)
}

Но насколько я знаю, это можно делать только вне класса. Я хотел бы найти способ объявить формат JSON и преобразовать в JSON внутри самого класса.


person lucperkins    schedule 09.04.2014    source источник


Ответы (1)


Вы можете представить себе это:

scala> import spray.json._
import spray.json._

scala> case class Foo(bar: String) {
  def toJson:JsValue = JsObject( "bar" -> JsString(bar) )
}
defined class Foo

scala> Foo("bar").toJson
res2: spray.json.JsValue = {"bar":"bar"}

Пока все хорошо, но это не вписывается в механизм классов типов Spray. Например, DSL маршрутизации Spray выдал бы вам ошибку типа, если бы вы затем попытались преобразовать Foo в / из JsValue (например, используя маршрут entity( as[Foo] ) { ... }). И имплициты, которые они уже подготовили для вас, для таких типов, как List и Set, не могут работать с Foo:

scala> import DefaultJsonProtocol._
import DefaultJsonProtocol._

scala> List(Foo("bar")).toJson
<console>:31: error: Cannot find JsonWriter or JsonFormat type class for List[Foo]
              List(Foo("bar")).toJson

потому что у них нет класса JsonFormat, который можно было бы использовать для преобразования Foo, как тот, который создал бы JsonFormat1(Foo).

Затем вы можете подумать о том, чтобы поместить формат внутри сопутствующего объекта Foo, поскольку сопутствующий объект класса в области видимости находится на неявном пути поиска, например:

object Foo extends DefaultJsonProtocol {
  implicit val fooFormat = jsonFormat1(Foo)
}
case class Foo(bar: String)

Но поскольку мы еще не закончили определение Foo на этом этапе, компилятор выдает нам ошибку типа:

[error]  found   : Foo.type
[error]  required: ? => ?
[error]  Note: implicit value fooFormat is not applicable here because it comes after the application point and it lacks an explicit result type

Добавление явного типа результата RootJsonFormat[Foo] не решает проблему:

[error]  found   : Foo.type
[error]  required: ? => Foo
[error]   implicit val fooFormat:RootJsonFormat[Foo] = jsonFormat1(Foo)

Уловка (спасибо, knutwalker!) Состоит в том, чтобы явно передать Foo.apply:

object Foo extends DefaultJsonProtocol {
  implicit val fooFormat = jsonFormat1(Foo.apply)
}
case class Foo(bar: String)
person AmigoNico    schedule 09.04.2014
comment
Сопутствующий объект является частью неявной области поиска. Достаточно определить его в компаньоне, он будет найден без явного импорта. - person knutwalker; 10.04.2014
comment
Кажется, я не могу заставить это работать. Я попытался создать объект Foo, который расширяет DefaultJsonProtocol и содержит implicit val barFormat = jsonFormat1(Bar), но, поскольку я еще не закончил определение Bar на этом этапе, компилятор недоволен. Если вы можете заставить это работать, вы заслуживаете зеленой галочки; Я с радостью узнаю ваш ответ! - person AmigoNico; 10.04.2014
comment
Foo.apply делает свое дело, то есть implicit val barFormat = jsonFormat1(Foo.apply). Я кратко изложил это: gist.github.com/knutwalker/10355424 - person knutwalker; 10.04.2014