问题描述
我正在使用Jackson with Kotlin来序列化和反序列化各种Kotlin data class
es。
我的data class
是非常简单的对象,可以使用标准的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" }
}""");
}
这确实有效;它会在测试用例中抛出RuntimeException
的不必要值someField
。
这似乎是错误的
以上真的是进行此验证的合理方法吗?
它具有非常不好的代码气味。我正在创建一个全新的ObjectMapper
,并且必须为时间戳反序列化之类的东西重新注册模块。
问题
感觉必须 一种更精简,更少冗余的方法来反序列化Kotlin data class
(或Java POJO),然后对对象的内容进行后验证,而无需完全重写反序列化机制。
在吗?我想不出一种简单的方法来从反序列化器内遵循ObjectMapper
的默认反序列化行为。
解决方法
已请求用于验证服务的通用工具:
https://github.com/FasterXML/jackson-databind/issues/2045
尽管尚未实施。大概这不仅限于基于注释,还可以进行可插拔的后处理(加上始终可以通过对AnnotationIntrospector
进行子类化来始终“伪造”注释)。
除此之外(尚不可用),对于POJO,如果可以通过它传递所有属性(构造函数或工厂方法),则@JsonCreator
的使用效果很好。但是,对于验证第三方类型而言,效果不是很好。