1. 为什么需要关注@JSONField和@JsonProperty?
在日常开发中,我们经常遇到Java对象与JSON数据相互转换的场景。比如前后端交互时,后端返回的User对象需要转换成JSON格式传给前端;或者调用第三方接口时,对方要求的字段名与我们系统内部的命名规范不一致。这时候就需要用到JSON序列化注解来"搭桥"。
我刚入行时就踩过一个坑:当时接手的老系统使用fastjson,我在实体类上加了@JsonProperty注解,结果发现根本不生效。排查了半天才发现原来项目用的是fastjson而不是Jackson。这个经历让我深刻认识到:选择正确的注解,首先要看项目使用的JSON库。
2. 核心区别:底层框架与基本用法
2.1 出身不同的两兄弟
@JsonProperty来自Jackson家族,是SpringBoot默认集成的JSON库。当你新建一个SpringBoot项目时,已经自动引入了jackson-databind依赖:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency>而@JSONField则是阿里巴巴FastJSON的产物,需要单独引入:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency>2.2 作用域对比
实际使用时,两者的作用位置有明显差异:
@JsonProperty只能用在属性字段上:
public class User { @JsonProperty("user_name") private String name; }@JSONField则灵活得多,支持三种位置:
public class User { // 方式1:字段上 @JSONField(name = "user_name") private String name; // 方式2:getter上 @JSONField(name = "user_name") public String getName() { return name; } // 方式3:setter上 @JSONField(name = "user_name") public void setName(String name) { this.name = name; } }3. 特殊场景处理能力对比
3.1 日期格式化实战
处理日期字段时,@JSONField明显更强大:
public class Order { // Jackson需要额外配置 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; // FastJSON直接支持 @JSONField(format = "yyyy-MM-dd HH:mm:ss") private Date updateTime; }实测发现,如果同时使用两个注解,FastJSON的format优先级更高。我曾经在迁移项目时遇到过两个注解混用导致的日期格式不一致问题,建议统一使用一种方案。
3.2 字段排除的两种方式
不想让某些字段出现在JSON中?两种注解各有妙招:
public class Product { // Jackson方式 @JsonIgnore private String internalCode; // FastJSON方式 @JSONField(serialize = false) private String secretKey; }注意一个小坑:当字段同时被@JsonIgnore和@JSONField标记时,FastJSON会优先遵守@JSONField的规则。
4. 性能实测与选型建议
4.1 基准测试数据
我用JMH做了简单测试(单位:ops/ms):
| 操作 | FastJSON | Jackson |
|---|---|---|
| 对象转JSON | 1253 | 897 |
| JSON转对象 | 1087 | 763 |
| 大对象序列化 | 856 | 621 |
从数据看FastJSON确实更快,但实际项目中差异可能没那么明显。除非是超高并发场景,否则建议更考虑功能需求。
4.2 选型决策树
根据我的经验,可以按这个流程决策:
- 如果是新SpringBoot项目 → 默认用
@JsonProperty - 如果需要处理复杂日期格式 → 优先考虑
@JSONField - 如果系统已有FastJSON依赖 → 统一用
@JSONField - 需要与旧系统保持兼容 → 沿用原有方案
特别提醒:混合使用两个注解时容易产生混乱。有次我在重构时发现一个实体类同时用了两种注解,导致:
- 用Jackson序列化时部分字段失效
- 用FastJSON反序列化时日期格式错乱 最后不得不花了半天时间统一注解标准。
5. 高频问题解决方案
5.1 boolean字段的is前缀问题
这是最常见的坑之一。假设有个字段:
private boolean isActive;生成的getter会自动变成isActive(),导致序列化后字段名变成"active"。解决方案:
// Jackson方案 @JsonProperty("isActive") private boolean isActive; // FastJSON方案 @JSONField(name = "isActive") private boolean isActive;5.2 默认值处理技巧
当JSON中缺少某个字段时:
public class Config { @JSONField(defaultValue = "default") private String theme; @JsonProperty(defaultValue = "light") private String mode; }注意:@JsonProperty的defaultValue只在反序列化时生效,而@JSONField的defaultValue会同时影响序列化和反序列化。
6. 高级特性深入应用
6.1 自定义序列化逻辑
两个注解都支持自定义序列化器,但实现方式不同:
// Jackson方式 public class User { @JsonSerialize(using = CustomSerializer.class) private String password; } // FastJSON方式 public class User { @JSONField(serializeUsing = CustomSerializer.class) private String password; }我曾经用这个特性实现过敏感数据自动脱敏:手机号显示为"138****1234",身份证号显示首尾各两位。
6.2 字段排序控制
API文档要求严格字段顺序时:
public class ApiResponse { @JSONField(ordinal = 1) private int code; @JSONField(ordinal = 2) private String message; @JsonProperty(index = 3) private Object data; }实测发现,当ordinal和index混用时,FastJSON的ordinal优先级更高。建议接口文档中的字段顺序最好与实体类定义顺序保持一致,减少不必要的注解。
7. 实际项目中的避坑指南
- 版本兼容性问题:FastJSON 1.2.36前后版本对
@JSONField的处理有差异,升级时要注意 - 循环引用问题:两个库处理方式不同,Jackson会抛出异常,FastJSON默认会用引用表示
- null值处理:通过配置可以控制是否序列化null值
- 跨微服务调用:服务间最好统一序列化方案
最近在改造一个老系统时,就遇到了FastJSON的parseObject方法自动trim字符串导致数据截断的问题。最后通过配置Feature.TrimString特性解决,这个细节在官方文档中很容易被忽略。