#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
вместо aval
), и все еще требует какого-то принудительного исполнения (например, черезscalastyle
)?