使用Jackson进行JSON序列化时,假如通过@JsonProperty注解指定了重复的字段(Java中的字段名称不同,但@JsonProperty注解属性中的名称相同),在不同情况下会有不同的结果,以下进行分析。
以下使用的Jackson版本为2.14.0。
假如在同一个类中通过@JsonProperty注解指定了重复的字段,如下所示(get/set方法略):
public class SameFieldsInSameClass {@JsonProperty("json_property")private String jsonProperty1;@JsonProperty("json_property")private String jsonProperty2;
}
使用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)
假如分别在父类与子类中通过@JsonProperty注解指定了重复的字段,如下所示(get/set方法略):
public class SameFieldsInDiffClassSuper {@JsonProperty("json_property")private String jsonPropertyInSuper;
}public class SameFieldsInDiffClassChild extends SameFieldsInDiffClassSuper {@JsonProperty("json_property")private String jsonPropertyInChild;
}
创建子类SameFieldsInDiffClassChild的对象,使用Jackson默认的ObjectMapper
进行JSON序列化的结果如下:
将父类SameFieldsInDiffClassSuper中的jsonPropertyInSuper字段值设为"super"
进行JSON序列化时,结果为"{}"
将子类中的jsonPropertyInChild字段值设为"child",进行JSON序列化时,结果为"{“json_property”:“child”}";
将父类SameFieldsInDiffClassSuper中的jsonPropertyInSuper字段值设为"super",将子类中的jsonPropertyInChild字段值设为"child",进行JSON序列化时,结果为"{“json_property”:“child”}";
通过以上结果可以看到,父类中的重复字段未出现在序列化结果中,子类中的重复字段出现在序列化结果中
。
默认情况下,Jackson允许父类与子类中通过@JsonProperty注解指定重复字段(Java中的字段名称不同,但@JsonProperty注解属性中的名称相同),但只有子类中的对应字段会出现在序列化结果中,父类中的对应字段不会出现在序列化结果中。
以上特性在日常开发中可能会造成问题,例如在父类与子类中分别存在使用下线划与驼峰形式的字段,都通过@JsonProperty注解将名称设置为下划线的形式,假如调用了父类对应字段的set方法,则JSON序列化结果不会包含对应的字段,JSON序列化的结果会与预期不相同。
为了禁止父类与子类中存在重复字段,可以通过以下方式对Jackson的JSON序列化处理进行修改。
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);}
POJOPropertiesCollector
子类重载_renameProperties()
方法并修改后的相关代码如下:
@Override
protected void _renameProperties(Map 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);}// 代码省略}}
}
Jackson在com.fasterxml.jackson.databind.introspect.BasicClassIntrospector
类的constructPropertyCollector()
方法中创建了需要使用的POJOPropertiesCollector类实例。
可以重载以上方法,使Jackson使用以上创建的POJOPropertiesCollector子类实例:
创建BasicClassIntrospector
的子类;
重载constructPropertyCollector()
方法,将返回对象类型由POJOPropertiesCollector
修改为以上POJOPropertiesCollector
的子类。
BasicClassIntrospector
子类重载constructPropertyCollector()
方法并修改后的代码如下:
public class TestBasicClassIntrospector extends BasicClassIntrospector {@Overrideprotected POJOPropertiesCollector constructPropertyCollector(MapperConfig> config,AnnotatedClass classDef,JavaType type,boolean forSerialization,AccessorNamingStrategy accNaming) {return new TestPOJOPropertiesCollector(config, forSerialization, type, classDef, accNaming);}
}
Jackson通过com.fasterxml.jackson.databind.Module
类的setClassIntrospector()
方法对ClassIntrospector
类的实例进行设置。
向ObjectMapper
注册Module
时,Jackson会调用Module
的setupModule()
方法,可在Module
子类的以上方法中通过setClassIntrospector()
方法设置以上创建的ClassIntrospector
子类实例:
创建SimpleModule
(属于Module
的子类)的子类;
重载setupModule()
方法,在调用父类的该方法后,调用setClassIntrospector()
方法设置以上创建的ClassIntrospector
子类实例。
SimpleModule
子类重载setupModule()
方法并修改后的代码如下:
public class TestSimpleModule extends SimpleModule {@Overridepublic void setupModule(SetupContext context) {super.setupModule(context);context.setClassIntrospector(new TestBasicClassIntrospector());}
}
在创建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);
通过以上方式使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)