Можно ли сериализовать как известный формат по умолчанию в пользовательском сериализаторе?

Мне нужно десериализовать ответ JSON, в котором одно из полей может быть установлено для разных объектов (только с одним общим полем). Реальная модель довольно сложна, но, например, мы можем представить ее двумя классами case, расширяющими seled trait:

sealed trait Item {
  val itemType: String
}

case class FirstItem(
  itemType: String = "FirstItem",
  firstProperties: SomeComplexType
) extends Item

case class SecondItem(
  itemType: String = "SecondItem",
  secondProperties: SomeOtherComplexType,
  secondName: String,
  secondSize: Int
) extends Item

Поскольку Json4s не знает, как обрабатывать этот объект, я написал собственный сериализатор:

object ItemSerializer extends CustomSerializer[Item](_ => ({
  case i: JObject => 
    implicit val formats: Formats = DefaultFormats
    (i \ "itemType").extract[String] match {
      case "FirstType" => i.extract[FirstItem]
      case "SecondItem" => i.extract[SecondItem]
    }
}, {
  case x: Item => x match {
    case f: FirstItem => JObject() //TODO
    case s: SecondItem => JObject() //TODO
  }
}))

Первая часть - десериализация не идеальна, так как сильно зависит от поля типа, но она подходит для моих нужд. Проблема во второй части - сериализации. В примерах я обнаружил, что люди обычно записывают каждое поле шаг за шагом, но обычно они сериализуют некоторые простые объекты. В моем случае этот объект имеет несколько уровней и в общей сложности более 60-80 полей, поэтому это приведет к довольно грязному и трудному для чтения коду. Поэтому мне интересно, есть ли лучший способ сделать это, поскольку и FirstItem, и SecondItem можно десериализовать, используя только DefaultFormats. Есть ли способ сообщить Json4s, что если объект соответствует заданному типу, он должен быть сериализован с форматом по умолчанию?


person Mateusz Gruszczynski    schedule 18.11.2019    source источник
comment
Не могли бы вы объяснить свой вариант использования? Может ли какое-то решение, такое как сохранение исходного ответа и его копирование, заменить сериализацию?   -  person Andriy Plokhotnyuk    schedule 18.11.2019
comment
Нет, копирование не поможет. Я получаю этот ответ от стороннего API, и мне нужно прочитать данные из вложенного объекта, но в зависимости от типа объекта он имеет разную структуру, и эти объекты могут быть возвращены либо как один объект, либо как массив объектов, где разрешены разные типы в пределах одного массива. Таких объектов в разных местах этого API немного, а в действительности типов гораздо больше, чем два. Поэтому я не хочу рассматривать это отдельно в каждом случае, мне нужно что-то общее.   -  person Mateusz Gruszczynski    schedule 18.11.2019
comment
@MateuszGruszczynski Готовы ли вы использовать другие библиотеки, кроме json4s?   -  person Ivan Stanislavciuc    schedule 19.11.2019
comment
@IvanStanislavciuc Я хотел бы сохранить json4, поскольку для этого потребуется либо много переписывать, либо ввести избыточную библиотеку. Мы использовали circe, но из-за большого количества сложных моделей компиляция была очень медленной (до 10-15 м в CI / CD), и, поскольку мы не делаем много запросов, это не обязательно должно быть быстрым во время выполнения, поэтому мы выбрал Json4s.   -  person Mateusz Gruszczynski    schedule 19.11.2019
comment
ВНИМАНИЕ: Json4s уязвим. под DoS / DoW-атаками!   -  person Andriy Plokhotnyuk    schedule 05.12.2019


Ответы (1)


Мне потребовалось немного покопаться в различных примерах, и оказалось, что это возможно и очень просто. Есть метод org.json4s.Extraction.decompose(), который обрабатывает все, например:

object ItemSerializer extends CustomSerializer[Item](_ => ({
  case i: JObject => 
    implicit val formats: Formats = DefaultFormats
    (i \ "itemType").extract[String] match {
      case "FirstType" => i.extract[FirstItem]
      case "SecondItem" => i.extract[SecondItem]
    }
}, {
  case x: Item => 
    implicit val formats: Formats = DefaultFormats
    x match {
      case f: FirstItem => Extraction.decompose(f)
      case s: SecondItem => Extraction.decompose(s)
    }
}))

Однако мое решение описанной проблемы было неправильным. Мне не нужно указывать дополнительные сериализаторы. Мне нужен был просто объект-компаньон для трейта, который содержит конструкторы для каждого формата данных, а Json4s отлично справляется со всем, например:

object Item{
  def apply(
    itemType: String, 
    firstProperties: SomeComplexType
  ): Item = FirstItem(itemType, firstProperties)

  def apply(
    itemType: String, 
    secondProperties: SomeOtherComplexType, 
    secondName: String, secondSize: Int
  ): Item = SecondItem(itemType, secondProperties, secondName, secondSize)
}
person Mateusz Gruszczynski    schedule 19.11.2019