• Jackson @JsonProperty重复字段处理


    1. 说明

    使用Jackson进行JSON序列化时,假如通过@JsonProperty注解指定了重复的字段(Java中的字段名称不同,但@JsonProperty注解属性中的名称相同),在不同情况下会有不同的结果,以下进行分析。

    以下使用的Jackson版本为2.14.0。

    2. 同一个类中存在重复字段

    假如在同一个类中通过@JsonProperty注解指定了重复的字段,如下所示(get/set方法略):

    public class SameFieldsInSameClass {
        @JsonProperty("json_property")
        private String jsonProperty1;
    
        @JsonProperty("json_property")
        private String jsonProperty2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用Jackson默认的ObjectMapper对以上类进行JSON序列化时,会出现以上异常,即Jackson禁止同一个类中存在重复字段

    com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Conflicting getter definitions for property "json_property": SameFieldsInSameClass#getJsonProperty1() vs SameFieldsInSameClass#getJsonProperty2()
    	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
    	at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1306)
    	at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1453)
    	at com.fasterxml.jackson.databind.SerializerProvider.findValueSerializer(SerializerProvider.java:550)
    	at com.fasterxml.jackson.databind.SerializerProvider.findTypedValueSerializer(SerializerProvider.java:828)
    	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:308)
    	at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4624)
    	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3869)
    	at TestJSONUtil.toDenseJsonStr(TestJSONUtil.java:10)
    	at SameFieldsInSameClass.main(SameFieldsInSameClass.java:27)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3. 父类与子类中存在重复字段

    假如分别在父类与子类中通过@JsonProperty注解指定了重复的字段,如下所示(get/set方法略):

    public class SameFieldsInDiffClassSuper {
        @JsonProperty("json_property")
        private String jsonPropertyInSuper;
    }
    
    public class SameFieldsInDiffClassChild extends SameFieldsInDiffClassSuper {
        @JsonProperty("json_property")
        private String jsonPropertyInChild;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建子类SameFieldsInDiffClassChild的对象,使用Jackson默认的ObjectMapper进行JSON序列化的结果如下:

    • 仅对父类字段赋值

    将父类SameFieldsInDiffClassSuper中的jsonPropertyInSuper字段值设为"super"

    进行JSON序列化时,结果为"{}"

    • 仅对子类字段赋值

    将子类中的jsonPropertyInChild字段值设为"child",进行JSON序列化时,结果为"{“json_property”:“child”}";

    • 同时对父类与子类字段赋值

    将父类SameFieldsInDiffClassSuper中的jsonPropertyInSuper字段值设为"super",将子类中的jsonPropertyInChild字段值设为"child",进行JSON序列化时,结果为"{“json_property”:“child”}";

    通过以上结果可以看到,父类中的重复字段未出现在序列化结果中,子类中的重复字段出现在序列化结果中

    4. 允许父类与子类中存在重复字段存在的问题

    默认情况下,Jackson允许父类与子类中通过@JsonProperty注解指定重复字段(Java中的字段名称不同,但@JsonProperty注解属性中的名称相同),但只有子类中的对应字段会出现在序列化结果中,父类中的对应字段不会出现在序列化结果中。

    以上特性在日常开发中可能会造成问题,例如在父类与子类中分别存在使用下线划与驼峰形式的字段,都通过@JsonProperty注解将名称设置为下划线的形式,假如调用了父类对应字段的set方法,则JSON序列化结果不会包含对应的字段,JSON序列化的结果会与预期不相同。

    5. 禁止父类与子类中存在重复字段

    为了禁止父类与子类中存在重复字段,可以通过以下方式对Jackson的JSON序列化处理进行修改。

    5.1. 修改POJOPropertiesCollector对重复字段的处理

    Jackson在找到重复字段时的处理由com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector类的_renameProperties()方法处理。

    可以重载以上方法,使Jackson找到重复字段时抛出异常,能够使存在重复字段的问题暴露出来:

    • 创建POJOPropertiesCollector的子类;

    • 重载_renameProperties()方法,修改“old.addAll(prop);”处理为抛出异常,其他代码保持不变;

    • 在静态代码块中判断Jackson组件的版本,当版本与预期不一致时抛出异常,使得Jackson版本发生变化时能够发现,可能需要重新调整_renameProperties()方法的代码。

    POJOPropertiesCollector子类静态代码块及构造函数示例代码如下:

    public class TestPOJOPropertiesCollector extends POJOPropertiesCollector {
        static {
            if (PackageVersion.VERSION.getMajorVersion() != 2 ||
                    PackageVersion.VERSION.getMinorVersion() != 14 ||
                    PackageVersion.VERSION.getPatchLevel() != 0) {
                throw new RuntimeException("jackson版本发生变化");
            }
        }
    
        protected TestPOJOPropertiesCollector(MapperConfig<?> config, boolean forSerialization, JavaType type, AnnotatedClass classDef, AccessorNamingStrategy accessorNaming) {
            super(config, forSerialization, type, classDef, accessorNaming);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    POJOPropertiesCollector子类重载_renameProperties()方法并修改后的相关代码如下:

    @Override
    protected void _renameProperties(Map<String, POJOPropertyBuilder> props) {
    	// 代码省略
    	// and if any were renamed, merge back in...
    	if (renamed != null) {
    		for (POJOPropertyBuilder prop : renamed) {
    			String name = prop.getName();
    			POJOPropertyBuilder old = props.get(name);
    			if (old == null) {
    				props.put(name, prop);
    			} else {
    				// 在这里进行修改,抛出异常
    				throw new RuntimeException("出现重复字段");
    				// old.addAll(prop);
    			}
    			// 代码省略
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    5.2. 通过ClassIntrospector使用指定的POJOPropertiesCollector

    Jackson在com.fasterxml.jackson.databind.introspect.BasicClassIntrospector类的constructPropertyCollector()方法中创建了需要使用的POJOPropertiesCollector类实例。

    可以重载以上方法,使Jackson使用以上创建的POJOPropertiesCollector子类实例:

    • 创建BasicClassIntrospector的子类;

    • 重载constructPropertyCollector()方法,将返回对象类型由POJOPropertiesCollector修改为以上POJOPropertiesCollector的子类。

    BasicClassIntrospector子类重载constructPropertyCollector()方法并修改后的代码如下:

    public class TestBasicClassIntrospector extends BasicClassIntrospector {
        @Override
        protected POJOPropertiesCollector constructPropertyCollector(MapperConfig<?> config,
                                                                     AnnotatedClass classDef,
                                                                     JavaType type,
                                                                     boolean forSerialization,
                                                                     AccessorNamingStrategy accNaming) {
            return new TestPOJOPropertiesCollector(config, forSerialization, type, classDef, accNaming);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.3. 通过Module使用指定的ClassIntrospector

    Jackson通过com.fasterxml.jackson.databind.Module类的setClassIntrospector()方法对ClassIntrospector类的实例进行设置。

    ObjectMapper注册Module时,Jackson会调用ModulesetupModule()方法,可在Module子类的以上方法中通过setClassIntrospector()方法设置以上创建的ClassIntrospector子类实例:

    • 创建SimpleModule(属于Module的子类)的子类;

    • 重载setupModule()方法,在调用父类的该方法后,调用setClassIntrospector()方法设置以上创建的ClassIntrospector子类实例。

    SimpleModule子类重载setupModule()方法并修改后的代码如下:

    public class TestSimpleModule extends SimpleModule {
        @Override
        public void setupModule(SetupContext context) {
            super.setupModule(context);
            context.setClassIntrospector(new TestBasicClassIntrospector());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.4. 在ObjectMapper中注册Module

    在创建Jackson的com.fasterxml.jackson.databind.ObjectMapper对象后,可以通过registerModule()方法注册需要使用的Module

    创建ObjectMapper对象后,通过registerModule()注册以上创建的SimpleModule的子类,可以使以上BasicClassIntrospector子类、POJOPropertiesCollector子类依次生效,使Jackson能够发现重复字段时抛出指定的异常。

    使Jackson进行JSON序列化时禁止父类与子类中存在重复字段的示例代码如下:

    ObjectMapper mapper = new ObjectMapper();
    mapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));
    mapper.registerModule(new TestSimpleModule());
    return mapper.writeValueAsString(value);
    
    • 1
    • 2
    • 3
    • 4

    6. 禁止父类与子类中存在重复字段的效果

    通过以上方式使Jackson禁止父类与子类中存在重复字段,进行JSON序列化时,会出现指定的异常,如下所示:

    Exception in thread "main" java.lang.RuntimeException: 出现重复字段
    	at TestPOJOPropertiesCollector._renameProperties(TestPOJOPropertiesCollector.java:77)
    	at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.collectAll(POJOPropertiesCollector.java:436)
    	at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getJsonValueAccessor(POJOPropertiesCollector.java:270)
    	at com.fasterxml.jackson.databind.introspect.BasicBeanDescription.findJsonValueAccessor(BasicBeanDescription.java:258)
    	at com.fasterxml.jackson.databind.ser.BasicSerializerFactory.findSerializerByAnnotations(BasicSerializerFactory.java:391)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._createSerializer2(BeanSerializerFactory.java:225)
    	at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:174)
    	at com.fasterxml.jackson.databind.SerializerProvider._createUntypedSerializer(SerializerProvider.java:1501)
    	at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1449)
    	at com.fasterxml.jackson.databind.SerializerProvider.findValueSerializer(SerializerProvider.java:550)
    	at com.fasterxml.jackson.databind.SerializerProvider.findTypedValueSerializer(SerializerProvider.java:828)
    	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:308)
    	at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4624)
    	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3869)
    	at TestJSONUtil.toDenseJsonStrForbidDuplicateFields(TestJSONUtil.java:33)
    	at SameFieldsInDiffClassChild.main(SameFieldsInDiffClassChild.java:30)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    前端如何去除本地版本号缓存
    深入浅出WMS之入库流程解析
    CDGA|如何加强数字政府建设?
    什么是线程池?
    阿里巴巴springcloud的gateway网关如何用继承接口WebExceptionHandler定义一个json格式的404错误页面实例
    Bert不完全手册7. 为Bert注入知识的力量 Baidu-ERNIE & THU-ERNIE & KBert
    当LCC画龙时,新老车企分别在想什么?
    Tomcat 调优之从 Linux 内核源码层面看 Tcp backlog
    MATLAB continue语句
    RabbitMQ之死信队列
  • 原文地址:https://blog.csdn.net/a82514921/article/details/128044281