使用 json4s 将 Java 枚举一般序列化为 json
Generically Serialize Java Enums to json using json4s
我们的 finatra 应用程序使用 json4s 在我们的控制器响应中将对象序列化为 jsons。但是,我注意到在尝试序列化枚举时,它会创建一个空对象。
我看到这个响应可以解决我的问题,但必须为每个枚举复制:
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 枚举实例?
由于类型安全限制,我认为没有干净的解决方案。尽管如此,如果您对依赖于 Java 使用类型擦除这一事实的骇人听闻的解决方案感到满意,那么这里似乎可行:
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
?
有两件重要的事情:
扩展 Serializer
instead of CustomSerializer
。这很重要,因为它允许创建一个可以处理所有 Enum
类型的非泛型实例。这是有效的,因为 Serializer.deserialize
创建的函数接收 TypeInfo
作为参数,因此它可以分析运行时 class.
Json4sFakeEnum
破解。从高级的角度来看,只需要给定枚举的 Class
就足以获取所有名称,因为它们存储在 Class
对象中。然而,在实现细节级别上,最简单的访问方法是使用具有以下签名的 Enum.valueOf
方法:
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
的特定子 class 来进行 valueOf
调用。
我们的 finatra 应用程序使用 json4s 在我们的控制器响应中将对象序列化为 jsons。但是,我注意到在尝试序列化枚举时,它会创建一个空对象。
我看到这个响应可以解决我的问题,但必须为每个枚举复制:
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 枚举实例?
由于类型安全限制,我认为没有干净的解决方案。尽管如此,如果您对依赖于 Java 使用类型擦除这一事实的骇人听闻的解决方案感到满意,那么这里似乎可行:
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
?
有两件重要的事情:
扩展
Serializer
instead ofCustomSerializer
。这很重要,因为它允许创建一个可以处理所有Enum
类型的非泛型实例。这是有效的,因为Serializer.deserialize
创建的函数接收TypeInfo
作为参数,因此它可以分析运行时 class.Json4sFakeEnum
破解。从高级的角度来看,只需要给定枚举的Class
就足以获取所有名称,因为它们存储在Class
对象中。然而,在实现细节级别上,最简单的访问方法是使用具有以下签名的Enum.valueOf
方法:
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
的特定子 class 来进行 valueOf
调用。