当前位置: 首页 > news >正文

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;
}

使用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)

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

假如分别在父类与子类中通过@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”}";

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

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);
    }

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);
			}
			// 代码省略
		}
	}
}

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);
    }
}

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());
    }
}

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);

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)

相关文章:

  • 元组啊,不就是不可变的列表吗?
  • Java练习题
  • 蓝桥杯跑步锻炼.c语言
  • java计算机毕业设计妇女健康保健系统源码+mysql数据库+系统+lw文档+部署
  • 第十四届蓝桥杯(web应用开发) 模拟赛2期 -大学组
  • 用Python代码画世界杯吉祥物拉伊卜(附代码)
  • 大规模异构图召回在美团到店推荐广告的应用
  • 在大厂工作是这样的
  • [附源码]SSM计算机毕业设计民宿客栈管理系统JAVA
  • FPGA片内RAM读写测试实验+逻辑分析仪ila
  • 【数据结构】七种排序方法,一篇文章掌握
  • 【web前端开发】HTML知识点超详细总结
  • C++ 语言学习 day11 复习(3)
  • 网络编程基础知识
  • 【用户画像】应用场景
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【面试系列】之二:关于js原型
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • canvas 绘制双线技巧
  • conda常用的命令
  • Swift 中的尾递归和蹦床
  • vagrant 添加本地 box 安装 laravel homestead
  • Vue ES6 Jade Scss Webpack Gulp
  • windows下mongoDB的环境配置
  • 从0实现一个tiny react(三)生命周期
  • 多线程事务回滚
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 记录:CentOS7.2配置LNMP环境记录
  • 使用SAX解析XML
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 移动端唤起键盘时取消position:fixed定位
  • 在weex里面使用chart图表
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • # C++之functional库用法整理
  • # Panda3d 碰撞检测系统介绍
  • #{}和${}的区别是什么 -- java面试
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • $.ajax,axios,fetch三种ajax请求的区别
  • ${factoryList }后面有空格不影响
  • (10)STL算法之搜索(二) 二分查找
  • (编译到47%失败)to be deleted
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (分布式缓存)Redis持久化
  • (南京观海微电子)——I3C协议介绍
  • (强烈推荐)移动端音视频从零到上手(上)
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (顺序)容器的好伴侣 --- 容器适配器
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • 、写入Shellcode到注册表上线
  • .axf 转化 .bin文件 的方法
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .net core Swagger 过滤部分Api
  • .net 设置默认首页
  • .NetCore部署微服务(二)