Сложность сериализации immutable.Map с помощью spray-json

Заранее извиняюсь за кросс-постинг: я отправил этот вопрос в список пользователей спрея, но список, похоже, модерируется, и, кажется, никого нет дома. Надеюсь, SO - лучшее место.

У меня возникают трудности с сериализацией сложных структур данных с помощью spray-json. Например, простой immutable.Map[String,String] работает нормально, а immutable.Map[String,Foo], где Foo — класс case, который я определяю, — нет. Документация оставила у меня впечатление, что мне просто нужно определить JsonFormat для Foo, и я буду готов к работе.

Вот пример кода:

import spray.json._
import DefaultJsonProtocol._

case class Foo(hi: String)

object FooProtocol extends DefaultJsonProtocol {
  implicit val fooFormat: JsonFormat[Foo] = jsonFormat1(Foo)
}
import FooProtocol._

object Thing {
  def toSomething = {
    Map("foo" -> Foo("bar"), "baz" -> Foo("quux")).toJson
  }
}

Что дает следующую ошибку компилятора:

[info] Compiling 1 Scala source to C:\spraytest\target\scala-2.10\classes...
[error] C:\spraytest\src\main\scala\Foo.scala:12: Cannot find JsonWriter or JsonFormat type class for scala.collection.immutable.Map[String,Foo]
[error]     Map("foo" -> Foo("bar"), "baz" -> Foo("quux")).toJson
[error]                                                    ^
[error] one error found
[error] (compile:compile) Compilation failed
[error] Total time: 5 s, completed Jun 26, 2014 11:40:26 AM

Я в тупике.


person Dan Barowy    schedule 26.06.2014    source источник


Ответы (3)


Переименуйте свой FooProtocol в Foo (это сделает его сопутствующим объектом ~> неявная область бесплатно) и вместо наследования просто import DefaultJsonProtocol._, например:

import spray.json.DefaultJsonProtocol._
case class Foo(hi: String)
object Foo {
  implicit val fooJson = jsonFormat1(Foo.apply)
}

Теперь вам нужно импортировать правильные маршалеры в область с вашим маршрутом Spray. Marshaller для String можно найти в spray DefaultJsonProtocol, а marshaller Foos можно использовать из его сопутствующего объекта, поэтому единственное, что вам нужно импортировать, это spray.httpx.SprayJsonSupport, это должно помочь.

А можно оставить как есть и просто import FooProtocol. Но это не лучшее решение с точки зрения дизайна.

Обновить

Если вы посмотрите на подпись toJson, вы увидите, что она запрашивает неявный JsonWriter, который генерируется методом jsonFormat. Сопутствующий объект решает проблему, потому что компилятор scala включает его в неявную область разрешения, это хорошая практика для размещения ваших импликатов там, потому что в этом случае вам не нужно делать явный импорт, например import FooProtocol._

Что касается маршалеров Map и String, взгляните на трейт DefaultJsonProtocol, он расширяет BasicFormats и CollectionFormats, у которых есть маршаллеры для String и Map соответственно. Единственное, что вам нужно добавить, это собственный маршаллер для класса Foo.

Решение

Настоящая проблема заключалась в множественных имплицитных маршаллерах. Доступ ко всем стандартным имплицитам (в данном случае для Map и String) можно получить с помощью одного импорта DefaultJsonProtocol. Когда вы расширяете этот трейт, а затем импортируете его в текущую область, он также импортирует все стандартные маршаллеры. Таким образом, проблема заключалась в том, что у вас было несколько маршалеров в области видимости, что вызывало проблему с неоднозначными имплицитами для компилятора scala.

person 4lex1v    schedule 26.06.2014
comment
Ваш трюк с сопутствующим объектом решает проблему, но я затрудняюсь объяснить, почему это так. Кстати, я использую только spray-json, а не весь набор спреев, поэтому spray.httpx недоступен. - person Dan Barowy; 26.06.2014
comment
@DanBarowy я добавил свой комментарий к ответу - person 4lex1v; 26.06.2014
comment
Я случайно добавил отсутствующий import FooProtocol._ в свой исходный вопрос. Этот импорт может быть уродливым, но если то, что вы говорите, верно, то добавления этого импорта должно быть достаточно, чтобы код скомпилировался. Верно? Ошибка компилятора в моем примере, по-видимому, означает, что Scala не может найти неявное значение для Map, а не для Foo. Я не понимаю, как сопутствующий рефакторинг решает эту проблему. - person Dan Barowy; 26.06.2014
comment
@DanBarowy ваш FooProtocol расширяет DJP. Когда вы импортируете его в область видимости, компилятор Scala видит несколько маршалеров карт, это приводит к неоднозначности в механизме неявного разрешения, и Scala не может выбрать правильный маршаллер для карты и строки. - person 4lex1v; 26.06.2014
comment
@Alexlv проблема с наличием в области действия как DefaultJsonProtocol, так и FooProtocol оказывается реальным ответом. Если вы обновите свой ответ своим комментарием о двусмысленности (для будущих читателей), я с радостью присужу вам баллы. Спасибо! - person Dan Barowy; 27.06.2014
comment
@DanBarowy рад, что это помогло вам. Я обновил свой ответ фактическим решением - person 4lex1v; 27.06.2014

Насколько я знаю, для Map[String,_] нет сортировки по умолчанию. Вы можете либо попытаться явно преобразовать свои объекты в String (Map[String,String] можно сериализовать в Json), либо предоставить маршаллер для Map[String, Foo].

person Ashalynd    schedule 26.06.2014

Вам нужно будет создать объект для хранения карты, затем вы сможете сериализовать этот объект.

import spray.json._
import DefaultJsonProtocol._

case class Foo(hi: String)

case class Bar( something: Map[String,Foo])

object FooBarProtocol extends DefaultJsonProtocol {
  implicit val fooFormat: JsonFormat[Foo] = jsonFormat1(Foo)
  implicit val barFormat: JsonFormat[Bar] = jsonFormat1(Bar)
}

object Thing {
  def toSomething = {
    Bar(Map("foo" -> Foo("bar"), "baz" -> Foo("quux"))).toJson
  }
}
person Gangstead    schedule 26.06.2014
comment
Нет смысла делать дополнительный объект данных (Bar в вашем случае), все будет работать с простой Map[String, Foo] - person 4lex1v; 26.06.2014
comment
Ваш пример работает после добавления import FooBarProtocol._. Но я все еще озадачен. Если у DefaultJsonProtocol есть сериализаторы для Map и String, а я предоставляю один для Foo, зачем мне оборачивать объект? Разве не должно быть неявное уже в области видимости? - person Dan Barowy; 26.06.2014