【发布时间】:2021-02-21 20:55:28
【问题描述】:
我正在使用Jackson with Kotlin对各种Kotlin进行序列化和反序列化data classes。
我的 data classes 是非常简单的对象,可以使用标准 Jackson ObjectMapper 轻松序列化和反序列化,除了我想确保完成一些后验证作为反序列化。比如我要保证Thing.someField >= 0:
data class InnerThing(
val foo: String
);
data class Thing(
val someField: Int, // must not be negative
val innerThing: InnerThing
);
实现这一点的最简单方法似乎是覆盖相关类的StdDeserializer。
class ThingDeserializer : StdDeserializer<Thing>(Thing::class.java) {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Thing {
// Defer to the superclass to do the actual deserialization
// DOES NOT WORK because StdDeserializer.deserialize() is abstract
val t: Thing = super.deserialize(p, ctxt);
if (thing.someField < 0) {
throw RuntimeException("someField value must be >= 0");
}
return t;
}
}
…但那不起作用,因为StdDeserializer.deserialize() 是抽象的。这将我引向this related question。似乎在自定义反序列化程序中遵循默认反序列化行为非常困难。
据我所知,推迟默认反序列化行为的最直接方法是创建一个完全单独的ObjectMapper (!!!),使用 that 阅读课程,然后进行后期验证。
把我引向这个……
class ThingDeserializer : StdDeserializer<Thing>(Thing::class.java) {
// Create a whole separate ObjectMapper that doesn't override
// the deserializer for Thing:
private val defaultMapper = jacksonObjectMapper();
init {
// Need to reregister modules for things like ISO8601
// timestamp parsing:
defaultMapper.registerModule(JavaTimeModule());
}
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Thing {
// Use the non-overridden ObjectMapper to deserialize the object:
val t: Thing = defaultMapper.readValue(p, Thing::class.java);
// ... and then do validation afterwards
if (thing.someField < 0) {
throw RuntimeException("someField value must be >= 0");
}
return t;
}
}
fun main(args: Array<String>) {
val mapper = jacksonObjectMapper();
mapper.registerModule(JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.registerModule(
SimpleModule().apply {
addDeserializer(Thing::class.java, ThingDeserializer())
}
);
val test: Thing = mapper.readValue("""{
"someField": -1,
"innerThing": { "foo": "bar" }
}""");
}
这确实有效;它会在测试用例中为不需要的 someField 值抛出一个 RuntimeException。
这似乎是错误的
上述真的是一种合理的验证方式吗?
它有一种非常糟糕的代码气味。我正在创建一个全新的ObjectMapper,我必须重新注册模块以进行时间戳反序列化等操作。
问题
感觉必须有一种更理智、更少冗余的方法来反序列化 Kotlin data class(或 Java POJO),然后对对象的内容进行后验证,而无需重写完全反序列化机制。
有吗?我想不出一种简单的方法来从反序列化器中推迟ObjectMapper 的默认反序列化行为。
【问题讨论】:
-
如果
Thing的实例不是通过反序列化创建的,它是否有意义?也许您只需将此字段类型更改为UInt 或将此检查移至init块? -
谢谢@МихаилНафталь。在这种情况下,由于整个数据结构的版本控制相当复杂,其中包括嵌套元素,因此更容易验证整个结构,而不是单独的
data classes 被实例化。但是,是的……一些验证可以在这些类的init块中完成。 -
@oddbjorn 在下面的答案中的简单机制允许非常理智的验证(最少的代码)。一旦所需的设置到位,您只需要在您的类中使用
validate()方法..
标签: json kotlin jackson introspection jackson-modules