缺少序列化器时,密封特征和对象枚举的快速JSON4s序列化失败

huo*_*uon 9 reflection serialization 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")
        })
    )
Run Code Online (Sandbox Code Playgroud)

这很棒,添加到以下格式后效果很好:

{
  implicit val formats = DefaultFormats + FooSerializer
  Serialization.write(X) // "x"
}
Run Code Online (Sandbox Code Playgroud)

这很棒!

问题

如果未将序列化程序添加到格式中,则json4s将使用反射来创建字段的默认表示形式,这对于这些object没有字段的s 极为不利。它无声地执行此操作,似乎无法控制它。

{
  implicit val formats = DefaultFormats
  Serialization.write(X) // {}
}
Run Code Online (Sandbox Code Playgroud)

这是一个问题,因为直到很久以后才有迹象表明出了什么问题。如果没有碰巧捕获到这些无效/无用的数据,它们可能会通过网络发送或写入数据库。而且,这可能是从图书馆公开公开的,这意味着下游用户也必须记住它。

注意 这与不同read,后者会在失败时引发异常,因为Footrait没有任何有用的构造函数:

{
  implicit val formats = DefaultFormats
  Serialization.read[Foo]("\"x\"")
}
Run Code Online (Sandbox Code Playgroud)
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)
...
Run Code Online (Sandbox Code Playgroud)

有没有一种方法可以禁用{}这些对象的默认格式,或者将格式“烘焙”到对象本身?

例如,write抛出类似read这样的异常会很好,因为它会立即将问题标记给调用方。

Mar*_*lic 3

有一个古老的开放问题似乎提出了类似的问题,其中一位贡献者建议

您需要创建自定义解串器或序列化器

这听起来没有现成的方法可以改变默认行为。

方法 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>
Run Code Online (Sandbox Code Playgroud)

DefaultFormats并像这样提供定制

package object example {
  val DefaultFormats = Serialization.formats(NoTypeHints) + FooSerializer
}
Run Code Online (Sandbox Code Playgroud)

这将允许我们像这样序列化 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))
Run Code Online (Sandbox Code Playgroud)

哪个应该输出

{"foo":"x"}
"x"
"y"
Run Code Online (Sandbox Code Playgroud)

如果我们尝试 import org.json4s.DefaultFormats,那么 Scalastyle 应该会引发以下错误:

Import from illegal package: Please use example.DefaultFormats instead of org.json4s.DefaultFormats
Run Code Online (Sandbox Code Playgroud)

方法 2:烘焙非嵌套值的序列化

也许我们可以通过定义委托的write方法将格式“烘焙”到对象中FooSerialization.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
Run Code Online (Sandbox Code Playgroud)

请注意我们如何将传递FooSerializer格式硬编码为write. 现在我们可以序列化

println(X.write)
println(Y.write)
Run Code Online (Sandbox Code Playgroud)

哪个应该输出

"x"
"y"
Run Code Online (Sandbox Code Playgroud)

方法3:提供自定义DefaultFormats旁边org.json4s.DefaultFormats

我们也可以尝试DefaultFormats在我们自己的包中定义自定义,如下所示

package example

object DefaultFormats extends DefaultFormats {
  override val customSerializers: List[Serializer[_]] = List(FooSerializer)
}
Run Code Online (Sandbox Code Playgroud)

这将允许我们像这样序列化 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))
Run Code Online (Sandbox Code Playgroud)

哪个应该输出

{"foo":"x"}
"x"
"y"
Run Code Online (Sandbox Code Playgroud)

有两种默认格式org.json4s.DefaultFormatsexample.DefaultFormats,至少会让用户必须在两者之间进行选择,如果说他们使用 IDE 自动导入它们。