
本文详解 jackson 中 `@jsonproperty` 的双向作用机制,解决 json 下划线字段(如 `country_code`)与 java 驼峰属性(如 `countrycode`)在序列化与反序列化中的映射冲突问题,并提供零冗余、可维护的双 pojo 设计方案。
在使用 Jackson 处理 REST API 时,一个常见痛点是:服务端返回的 JSON 字段采用蛇形命名(snake_case),而团队 Java 代码规范强制要求驼峰命名(camelCase)。此时若仅靠 @JsonProperty 单向标注,极易导致「能反序列化却无法序列化」或反之的问题——正如你在示例中遇到的:将 @JsonProperty("countryCode") 错误地用于 country_code 字段,结果反序列化失败,值为 null。
根本原因在于:@JsonProperty("xxx") 是双向注解——它既指定该 Java 属性应从 JSON 的 "xxx" 字段读取(反序列化),也指定该属性在序列化为 JSON 时应写入 "xxx" 字段。因此:
-
若你的 JSON 原文含 "country_code": "IN",而 POJO 写成:
@JsonProperty("countryCode") // ❌ 期望读取 "countryCode",但实际是 "country_code" private String country_code;Jackson 将找不到匹配字段,赋值为 null。
立即学习“Java免费学习笔记(深入)”;
-
正确做法是让 @JsonProperty 的值严格匹配 JSON 实际键名:
@JsonProperty("country_code") // ✅ 精确匹配输入 JSON private String countryCode; // ✅ Java 属性名用 camelCase
但这样仍存在新问题:当你将该对象作为响应体返回时,Jackson 会输出 "country_code": "IN",而非符合 API 规范的 "countryCode"。
✅ 推荐解决方案:分离关注点,定义专用的入参(In)与出参(Out)POJO
// 公共基础字段(无 JSON 映射逻辑)
@Data
public class AirportInfoBase implements Serializable {
private String icao;
private String iata;
private String name;
private String location;
private String country;
private String longitude;
private String latitude;
private String link;
private Integer status;
}
// 专用于接收 snake_case JSON 的入参类
@Data
public class AirportInfoIn extends AirportInfoBase {
@JsonProperty("country_code")
private String countryCode;
@JsonProperty("latitude")
private String latitudeStr; // 如需额外转换,可在此处理
// 可选:提供便捷构造器
public AirportInfoIn() {}
public AirportInfoIn(AirportInfoOut out) {
this.countryCode = out.getCountryCode();
this.icao = out.getIcao();
this.iata = out.getIata();
// ... 其他字段复制
}
}
// 专用于返回 camelCase JSON 的出参类
@Data
public class AirportInfoOut extends AirportInfoBase {
@JsonProperty("countryCode")
private String countryCode;
// 构造器:从 AirportInfoIn 安全转换
public AirportInfoOut(AirportInfoIn in) {
this.countryCode = in.getCountryCode();
this.icao = in.getIcao();
this.iata = in.getIata();
this.name = in.getName();
this.location = in.getLocation();
this.country = in.getCountry();
this.longitude = in.getLongitude();
this.latitude = in.getLatitude();
this.link = in.getLink();
this.status = in.getStatus();
}
}关键优势:
- ✅ AirportInfoIn 精准消费第三方 API 的蛇形 JSON;
- ✅ AirportInfoOut 精准输出符合内部规范的驼峰 JSON;
- ✅ 通过继承复用公共字段,避免重复定义;
- ✅ 转换逻辑集中可控,便于添加校验、格式化(如字符串转 BigDecimal、时间解析等);
- ✅ 完全规避 @JsonProperty 双向语义引发的歧义。
⚠️ 注意事项:
- 不要依赖 Jackson 的 PropertyNamingStrategies.SNAKE_CASE 全局配置来“自动转换”——它会对所有字段生效,易引发意外覆盖(如已有 userId 字段被转为 user_id);
- Lombok 的 @Data 自动生成 getter/setter,确保 @JsonProperty 标注的字段名与生成方法名一致(如 countryCode → getCountryCode());
- 若需深度嵌套或复杂转换,建议配合 @JsonCreator 和 @JsonProperty 构造器注入,提升不可变性与线程安全性。
总结:JSON 绑定不是“一次标注,处处适用”,而是“按需建模”。清晰区分数据流入(In)与流出(Out)契约,是构建健壮、可演进 API 集成层的基石。










