Форматы spray-json для иерархии классов с подклассом, который ссылается на базовый класс

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

Начнем с иерархии классов и форматов json, где проблемные части закомментированы:

object Filters {
  sealed trait Filter
  case class SimpleFilter(foo: String) extends Filter
  case class DoubleFilter(foo: String, bar: String) extends Filter
  implicit val simpleFormat = jsonFormat1(SimpleFilter)
  implicit val doubleFormat = jsonFormat2(DoubleFilter)

//  case class AndFilter(filters: List[Filter]) extends Filter
//  implicit val andFormat = lazyFormat(jsonFormat1(AndFilter))    

  // (would really use a type field, keeping simple for example)
  implicit val filterFormat = new RootJsonFormat[Filter] {
    override def write(obj: Filter): JsValue = obj match {
      case x: SimpleFilter => x.toJson
      case x: DoubleFilter => x.toJson
//    case x: AndFilter => x.toJson
    }

    override def read(json: JsValue): Filter = json.asJsObject.getFields("bar") match {
      case Seq(_) => json.convertTo[DoubleFilter]
      case Seq()  => json.convertTo[SimpleFilter]
    }
  }
}

Это компилируется и работает так, как ожидалось, я могу сериализовать и десериализовать конкретные подклассы фильтров как Filter без проблем.

Но давайте прокомментируем AndFilter материал. Теперь беда! Если объявление andFormat перед filterFormat (как указано выше), оно не будет компилироваться, потому что andFormat требуется filterFormat:

Ошибка: (17, 43) не удалось найти неявное значение для параметра свидетельства типа spray.json.DefaultJsonProtocol.JF [List [classpath.Filters.Filter]] implicit val andFormat = jsonFormat1 (AndFilter)

Изменение порядка на andFormat после filterFormat позволит компилировать вещи. Но, конечно, я также хочу добавить andFormat ссылки на формат filter, то есть case x: AndFilter => x.toJson в методе записи и все, что включает json.convertTo[AndFilter] в методе чтения. И это тоже не компилируется:

Ошибка: (23, 34) Не удается найти класс типа JsonWriter или JsonFormat для classpath.Filters.Filter с classpath.Filters.AndFilter case x: AndFilter => x.toJson

Я не могу найти способ обойти это. Я пробовал использовать метод spray-json lazyFormat, но это не помогает (работает только для рекурсивных ссылок на себя, а не для таких перекрестных ссылок). Любые идеи?


person ryryguy    schedule 03.03.2016    source источник


Ответы (2)


Я считаю, что следующая модификация решает проблему с неявным разрешением JsonFormat[AndFilter], указанную в вопросе.

implicit val andFormat: JsonFormat[AndFilter] = lazyFormat(jsonFormat1(AndFilter))

Обратите внимание, что нам необходимо предоставить явную аннотацию типа (т.е. _ 3_) для andFormat, чтобы SparyJsonSupport мог использовать его как экземпляр RootJsonFormat вместо JsonFormat, возвращаемого lazyFormat:

  import spray.json._
  import DefaultJsonProtocol._

  object Filters {
    sealed trait Filter
    case class SimpleFilter(foo: String) extends Filter
    case class DoubleFilter(foo: String, bar: String) extends Filter
    case class AndFilter(filters: List[Filter]) extends Filter

    implicit val simpleFormat = jsonFormat1(SimpleFilter)
    implicit val doubleFormat = jsonFormat2(DoubleFilter)
    implicit val andFormat: JsonFormat[AndFilter] = lazyFormat(jsonFormat1(AndFilter))

    implicit val filterFormat = new RootJsonFormat[Filter] {
      override def write(obj: Filter): JsValue = ???
      override def read(json: JsValue): Filter = ???
    }
  }

Подробнее: см. https://github.com/spray/spray-json#jsonformats-for-recursive-types.

person Adil Akhter    schedule 09.03.2016
comment
Превосходно! Я пробовал использовать lazyFormat, когда изначально играл с этим, но, может быть, без явной аннотации типа? Или, возможно, какая-то другая ошибка пользователя - в любом случае я не мог заставить ее работать. Спасибо за решение! - person ryryguy; 09.03.2016

Иногда нужно немного помочь компилятору. Добавление явного типа для filterFormat и явного write приведет к его компиляции.

sealed trait Filter
case class SimpleFilter(foo: String) extends Filter
case class DoubleFilter(foo: String, bar: String) extends Filter
case class AndFilter(filters: List[Filter])
implicit val simpleFormat = jsonFormat1(SimpleFilter)
implicit val doubleFormat = jsonFormat2(DoubleFilter)
implicit val andFormat    = jsonFormat1(AndFilter)

implicit val filterFormat: RootJsonFormat[Filter] = new RootJsonFormat[Filter] {
  override def write(obj: Filter): JsValue = obj match {
    case x: SimpleFilter => x.toJson
    case x: DoubleFilter => x.toJson
    case x: AndFilter    => andFormat.write(x)
  }

  override def read(json: JsValue): Filter = json.asJsObject.getFields("bar") match {
    case Seq(_) => json.convertTo[DoubleFilter]
    case Seq()  => json.convertTo[SimpleFilter]
  }
}
person Pim Verkerk    schedule 04.03.2016
comment
Ой. Ага! (В свою защиту я настроил протокол и установил десятки классов кейсов в нескольких иерархиях и как бы забыл, что вещи не должны быть неявными. :)) - person ryryguy; 05.03.2016
comment
К сожалению, пока это компилируется, это не работает. Попытка сериализовать AndFilter вызывает исключение нулевого указателя в spray.json.CollectionFormats, вызывая spray.json.PimpedAny.toJson, где оказывается, что неявное JsonWriter, которое должно быть форматом фильтра, является нулевым. Дополнительное примечание - мой исходный код, который скопировал Пим, не позаботился о AndFilter расширении Filter. Я отредактирую. - person ryryguy; 05.03.2016