Отказоустойчивая сериализация json4s запечатанных признаков и перечислений объектов при отсутствии сериализатора - PullRequest
9 голосов
/ 17 апреля 2019

Настройка

Я использую json4s 3.2.11 и Scala 2.11.

У меня есть перечисление, определенное с помощью sealed trait, и специальный сериализатор для него:

import org.json4s.CustomSerializer
import org.json4s.JsonAST.JString
import org.json4s.DefaultFormats
import org.json4s.jackson.Serialization

sealed trait Foo
case object X extends Foo
case object Y extends Foo

object FooSerializer
    extends CustomSerializer[Foo](
      _ =>
        ({
          case JString("x") => X
          case JString("y") => Y
        }, {
          case X => JString("x")
          case Y => JString("y")
        })
    )

Это замечательно и хорошо работает при добавлении в форматы:

{
  implicit val formats = DefaultFormats + FooSerializer
  Serialization.write(X) // "x"
}

Это замечательно!

Проблема

Если сериализатор не добавленв форматах json4s будет использовать отражение для создания представления полей по умолчанию, что крайне бесполезно для тех object, которые не имеют полей.Он делает это молча, по-видимому, без возможности управлять им.

{
  implicit val formats = DefaultFormats
  Serialization.write(X) // {}
}

Это проблематично, так как нет никаких признаков того, что пошло не так, намного позже.Эти недействительные / бесполезные данные могут быть отправлены по сети или записаны в базы данных, если тесты не обнаружат их.И это может быть открыто опубликовано в библиотеке, что означает, что последующие пользователи также должны помнить об этом.

NB.это отличается от read, который выдает исключение при неудаче, поскольку черта Foo не имеет никаких полезных конструкторов:

{
  implicit val formats = DefaultFormats
  Serialization.read[Foo]("\"x\"")
}
org.json4s.package$MappingException: No constructor for type Foo, JString(x)
  at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$constructor(Extraction.scala:417)
  at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$instantiate(Extraction.scala:468)
  at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$result$6.apply(Extraction.scala:515)
...

Вопрос

Есть лиспособ отключить форматирование {} по умолчанию для этих объектов или «запечь» форматирование для самого объекта?

Например, если write выбросить исключение, например read, будетхорошо, так как это немедленно отметит проблему для вызывающего абонента.

1 Ответ

3 голосов
/ 24 мая 2019

Существует старая открытая проблема , которая, похоже, задает аналогичный вопрос, когда один из авторов предлагает

вам необходимо создать собственный десериализаторили сериализатор

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

Метод 1: Запрещать форматы по умолчанию через Scalastyle

Попробуйте запретить импорт org.json4s.DefaultFormats, используя Scalastyle IllegalImportsChecker

 <check level="error" class="org.scalastyle.scalariform.IllegalImportsChecker" enabled="true">
  <parameters>
   <customMessage>Import from illegal package: Please use example.DefaultFormats instead of org.json4s.DefaultFormats</customMessage>
   <parameter name="illegalImports"><![CDATA[org.json4s.DefaultFormats]]></parameter>
  </parameters>
 </check>

и предоставьте пользовательские DefaultFormats, например,

package object example {
  val DefaultFormats = Serialization.formats(NoTypeHints) + FooSerializer
}

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

import example.DefaultFormats
implicit val formats = DefaultFormats
case class Bar(foo: Foo)
println(Serialization.write(Bar(X)))
println(Serialization.write(X))
println(Serialization.write(Y))

, который должен вывести

{"foo":"x"}
"x"
"y"

Если мы попытаемся импортировать org.json4s.DefaultFormats, то Scalastyle должен вызвать следующую ошибку:

Import from illegal package: Please use example.DefaultFormats instead of org.json4s.DefaultFormats

Метод 2: Запекание в сериализации для не вложенных значений

Возможно, мы могли бы "запекать" форматирование в объекты, определив метод write в Foo, который делегирует Serialization.write как

sealed trait Foo {
  object FooSerializer extends CustomSerializer[Foo](_ =>
      ({
        case JString("x") => X
        case JString("y") => Y
      }, {
        case X => JString("x")
        case Y => JString("y")
      })
  )

  def write: String = 
    Serialization.write(this)(DefaultFormats + FooSerializer)
}
case object X extends Foo
case object Y extends Foo

Обратите внимание, как мы жестко закодировали передачу формата FooSerializer в write.Теперь мы можем сериализовать с

println(X.write)
println(Y.write)

, который должен вывести

"x"
"y"

Метод 3: Предоставить пользовательский DefaultFormats наряду с org.json4s.DefaultFormats

Мы также можем попробовать определить пользовательскийDefaultFormats в нашем собственном пакете, например,

package example

object DefaultFormats extends DefaultFormats {
  override val customSerializers: List[Serializer[_]] = List(FooSerializer)
}

, который позволил бы нам сериализовать ADT, например, так:

import example.DefaultFormats
implicit val formats = DefaultFormats
case class Bar(foo: Foo)
println(Serialization.write(Bar(X)))
println(Serialization.write(X))
println(Serialization.write(Y))

, который должен выдавать

{"foo":"x"}
"x"
"y"

, имея дваформаты по умолчанию, org.json4s.DefaultFormats и example.DefaultFormats, по крайней мере, заставят пользователя выбирать между двумя, если, скажем, они используют IDE для их автоматического импорта.

...