如何让Jackson的@JsonAnySetter捕获所有属性(含已映射字段)
解决方案
方案一:手动初始化已映射字段(适合字段少的场景)
直接在实体类的初始化块里,把已声明的字段手动加入dynamicFields,配合@JsonAnySetter捕获未映射字段,就能让dynamicFields包含所有属性:
data class Student(val name: String) { val dynamicFields: MutableMap<String, Any?> = mutableMapOf() // 初始化时把已映射的name字段加入map init { dynamicFields["name"] = name } @JsonAnySetter fun setDynamicField(key: String, value: Any?) { dynamicFields[key] = value } }
测试用给定的JSON:
{ "name": "John", "address": "Wick" }
反序列化后,student.dynamicFields会同时包含name: "John"和address: "Wick",实体类的name字段也能正常访问。
如果有嵌套对象,只要嵌套的实体类也用同样的方式处理即可:
data class Address(val street: String) { val dynamicFields: MutableMap<String, Any?> = mutableMapOf() init { dynamicFields["street"] = street } @JsonAnySetter fun setDynamicField(key: String, value: Any?) { dynamicFields[key] = value } } data class Student(val name: String, val address: Address) { val dynamicFields: MutableMap<String, Any?> = mutableMapOf() init { dynamicFields["name"] = name dynamicFields["address"] = address } @JsonAnySetter fun setDynamicField(key: String, value: Any?) { dynamicFields[key] = value } }
方案二:自定义Jackson Module(适合多字段、嵌套场景,一劳永逸)
如果实体类字段多或者嵌套层级复杂,手动加字段太繁琐,可以自定义一个Jackson Module,让Spring自动注入,不用直接操作ObjectMapper,就能自动把所有已映射字段加入指定的Map。
步骤1:定义标记注解
先写一个注解,标记哪个Map字段用来存放所有属性:
@Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class AllFieldsMap
步骤2:实现自定义Module
这个Module会修改Jackson的反序列化逻辑,自动把所有已映射字段注入到标记了@AllFieldsMap的Map里:
class AllFieldsCollectorModule : SimpleModule() { override fun setupModule(context: SetupContext) { super.setupModule(context) context.addBeanDeserializerModifier(object : BeanDeserializerModifier() { override fun modifyDeserializer( config: DeserializationConfig, beanDesc: BeanDescription, deserializer: JsonDeserializer<*> ): JsonDeserializer<*> { // 只处理带有@AllFieldsMap字段的实体类 val targetMapField = beanDesc.beanClass.declaredFields.find { it.isAnnotationPresent(AllFieldsMap::class.java) } ?: return deserializer if (deserializer is BeanDeserializer) { return object : BeanDeserializer(deserializer) { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Any { // 先完成默认反序列化,得到实体对象 val bean = super.deserialize(p, ctxt) // 获取当前类所有已映射的属性名 val mappedFields = beanDesc.findProperties().map { it.name } mappedFields.forEach { fieldName -> // 反射获取字段值 val field = beanDesc.beanClass.getDeclaredField(fieldName) field.isAccessible = true val fieldValue = field.get(bean) // 将字段名和值存入目标Map targetMapField.isAccessible = true @Suppress("UNCHECKED_CAST") val allFieldsMap = targetMapField.get(bean) as MutableMap<String, Any?> allFieldsMap[fieldName] = fieldValue } return bean } } } return deserializer } }) } }
步骤3:在Spring中注册Module
写一个配置类,把自定义Module注册到Spring的Bean容器,Jackson会自动加载这个Module:
@Configuration class JacksonConfig { @Bean fun allFieldsCollectorModule(): Module { return AllFieldsCollectorModule() } }
步骤4:改造实体类
给存放所有字段的Map加上@AllFieldsMap注解,保留@JsonAnySetter处理未映射字段:
data class Student(val name: String) { @AllFieldsMap val dynamicFields: MutableMap<String, Any?> = mutableMapOf() @JsonAnySetter fun setDynamicField(key: String, value: Any?) { dynamicFields[key] = value } } // 嵌套对象同样处理 data class Address(val street: String, val zipCode: String) { @AllFieldsMap val dynamicFields: MutableMap<String, Any?> = mutableMapOf() @JsonAnySetter fun setDynamicField(key: String, value: Any?) { dynamicFields[key] = value } }
这样不管实体类有多少字段,或者嵌套多少层,反序列化后dynamicFields都会自动包含所有已映射和未映射的属性,不用手动维护字段列表。
内容的提问来源于stack exchange,提问作者wjtk
相关产品推荐
相关产品推荐

