Сбой-быстрая сериализация json4s запечатанного признака и перечисления объектов при отсутствии сериализатора

#scala #serialization #reflection #json4s

#scala #сериализация #отражение #json4s

Вопрос:

Настройка

Я использую 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) // {}
}
 

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

ПРИМЕЧАНИЕ. это отличается от 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:

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

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

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

Метод 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 для их автоматического импорта.

Комментарии:

1. Этот write подход хорош для преобразования отдельных значений, но я думаю, что он не работает для вложенных значений (если у меня есть case class Bar(foo: Foo) , он должен сериализоваться в {"foo": "x"} , а не {"foo": {}} ), и все равно нужно помнить об использовании этой write функции. Спасибо, что нашли проблему! К сожалению, он открыт уже почти 6 лет.

2. Я отредактировал ответ с помощью метода Scalastyle, который позволил бы сериализацию с вложенным ADT, а также позволил пользователям продолжать использовать write .

3. Приятно; метод scalastyle интересен! Я думаю, что это разумный обходной путь, но я не большой поклонник, так как для этого требуется, чтобы любой, кто использует библиотеку, выполнял эту настройку (но, по крайней мере, это только один раз).). Чем метод 3 отличается от метода 1? Похоже, что это просто альтернативный способ указать соответствующий DefaultFormats объект (как object вместо a val ), и все еще требует какого-то принудительного исполнения (например, через scalastyle )?