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

时间:2019-04-17 03:55:04

标签: scala serialization reflection 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) // {}
}

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

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 个答案:

答案 0 :(得分:3)

有一个古老的open issue似乎在问类似的问题,其中contributors之一建议

  

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

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

方法1:通过Scalastyle禁止使用默认格式

尝试禁止使用Scalastyle IllegalImportsChecker导入UNWIND $filenames AS filename LOAD CSV WITH HEADERS FROM "file:///"+filename+".csv" AS data CREATE (f:File {filename:filename})-[:FOLLOWS]->(r:Row) SET r.csv_row = data

org.json4s.DefaultFormats

并像这样提供自定义 <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

这将允许我们像这样序列化ADT

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

应输出

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" ,则Scalastyle应该会引发以下错误:

org.json4s.DefaultFormats

方法2:进行序列化以获取非嵌套值

也许我们可以通过在Import from illegal package: Please use example.DefaultFormats instead of org.json4s.DefaultFormats 中定义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)

方法3:在"x" "y" 旁边提供自定义DefaultFormats

我们也可以尝试像这样在我们自己的包中定义自定义org.json4s.DefaultFormats

DefaultFormats

这将允许我们像这样序列化ADT

package example

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

应输出

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两种默认格式,至少会使用户不得不在两者之间进行选择,如果说,他们使用IDE来自动导入它们。