使用json4s将Java Enums一般序列化为json

时间:2018-01-11 17:17:41

标签: scala serialization enums json4s

我们的finatra应用程序使用json4s将对象序列化为控制器响应中的jsons。但是,我注意到在尝试序列化枚举时,它会创建一个空对象。

我看到这个响应可以解决我的问题但是必须为每个枚举复制: https://stackoverflow.com/a/35850126/2668545

class EnumSerializer[E <: Enum[E]](implicit ct: Manifest[E]) extends CustomSerializer[E](format ⇒ ({
  case JString(name) ⇒ Enum.valueOf(ct.runtimeClass.asInstanceOf[Class[E]], name)
}, {
  case dt: E ⇒ JString(dt.name())
}))

// first enum I could find
case class X(a: String, enum: java.time.format.FormatStyle)
implicit val formats = DefaultFormats + new EnumSerializer[java.time.format.FormatStyle]()

// {"a":"test","enum":"FULL"}
val jsonString = Serialization.write(X("test", FormatStyle.FULL))
Serialization.read[X](jsonString)

是否有办法制作一个通用的自定义序列化程序,通过在序列化为json时获取其.name()值来处理所有java枚举实例?

1 个答案:

答案 0 :(得分:1)

由于类型安全限制,我认为没有一个干净的解决方案。如果你对一个依赖于Java使用类型擦除这一事实的hacky解决方案没问题,那么这里似乎有效:

class EnumSerializer() extends Serializer[Enum[_]] {
  override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Enum[_]] = {
    // using Json4sFakeEnum is a huge HACK here but it seems to  work
    case (TypeInfo(clazz, _), JString(name)) if classOf[Enum[_]].isAssignableFrom(clazz) => Enum.valueOf[Json4sFakeEnum](clazz.asInstanceOf[Class[Json4sFakeEnum]], name)
  }

  override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
    case v: Enum[_] => JString(v.name())
  }
}

其中Json4sFakeEnum实际上是在Java中定义的假enum(实际上任何enum都应该有效,但我更愿意明确伪造)

enum Json4sFakeEnum {
}

使用此类定义的示例与您的类似

// first enum I could find
case class X(a: String, enum: java.time.format.FormatStyle)

def js(): Unit = {
  implicit val formats = DefaultFormats + new EnumSerializer()

  val jsonString = Serialization.write(X("test", FormatStyle.FULL))
  println(s"jsonString '$jsonString'")
  val r = Serialization.read[X](jsonString)
  println(s"res ${r.getClass} '$r'")
}

产生以下输出:

  

jsonString'{“a”:“test”,“enum”:“FULL”}'   
res class so.Main $ X'X(test,FULL)'

更新或如何运作以及您需要Json4sFakeEnum的原因?

有两件重要的事情:

  1. 扩展Serializer而不是CustomSerializer。这很重要,因为它允许创建一个可以处理所有Enum类型的非泛型实例。这是有效的,因为Serializer.deserialize创建的函数接收TypeInfo作为参数,因此它可以分析运行时类。

  2. Json4sFakeEnum黑客。从高级别的角度来看,仅使用给定枚举的Class来获取所有名称就足够了,因为它们存储在Class对象中。但是在实现细节级别上,最简单的访问方法是使用具有以下签名的Enum.valueOf方法:

  3. public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
    

    这里不幸的部分是它有一个通用的签名,并且有一个限制T extends Enum<T>。这意味着即使我们拥有正确的Class对象,我们知道的最佳类型仍然是Enum[_],并且不符合extends Enum<T>的自引用限制。另一方面,Java使用类型擦除,因此valueOf实际上编译为类似

    public static Enum<?> valueOf(Class<Enum<?>> enumType, String name)
    

    这意味着如果我们只是欺骗编译器允许我们调用valueOf,那么在运行时一切都会好的。这就是Json4sFakeEnum出现的地方:我们只需要在编译时知道Enum的特定子类就可以进行valueOf调用。