@JsonProperty 和 @JSONField注解的使用方法及其异同?
目录一. 区别对比二. 代码举例说明2.1 准备实体类2.2 不加注释2.3 JsonProperty2.4 JSONField三. 全局映射解决方案3.1 fastjson 包3.2 jackson 包四. Serializable 接口(多学一招)总结概括在实际项目开发中有时候我们需要将实体类型数据转化为 Json 串对方希望接收的 Json 数据中的 key 可能不是我们实体类定义的 key 例如我们后端定义的是createDate但是对方接收的数据 key 希望是 date还有一些时候我们在对接第三方API的时候对方返回的往往是 Json 数据如果其中含有下划线通常与Java 的实体类驼峰定义有差异导致我们反序列化进行存表时会有部分字段无法直接映射需要在代码中进行xxx.setXXX(xxx.getXXX)简单来说就是将无法直接映射的通过 get 方法获取出来再 set 到对应的实体对象中所以本篇文章就来简要说一说如何通过注解来解决序列化、反序列化的字段映射问题。一. 区别对比下面表格是这两个注解的主要区别小伙伴们主要关注字段重命名日期格式化控制序列化方向这三个就够了。特性JsonProperty (Jackson)JSONField(Fastjson)所属库Jackson (Spring Boot默认集成)Fastjson (阿里巴巴开源)字段重命名JsonProperty(new_name)JSONField(name new_name)控制序列化方向access属性READ_ONLY只读WRITE_ONLY只写serialize/deserialize属性serializefalse不序列化deserializefalse不反序列化日期格式化需配合JsonFormatJsonFormat(patternyyyy-MM-dd)直接支持JSONField(formatyyyy-MM-dd)默认值defaultValue属性JsonProperty(defaultValue unknown)不支持字段顺序需配合JsonPropertyOrder注解ordinal属性JSONField(ordinal 1)自定义序列化通过JsonSerialize指定自定义序列化器serializeUsing属性JSONField(serializeUsing MySerializer.class)二. 代码举例说明2.1 准备实体类如下定义了一个 Student 学生类简单定义了几个属性数据类型也是项目中较为常见的几种。注解暂时都注释掉了下面我们在做对应的注解测试的时候把相对应的注解打开即可。Data public class Student implements Serializable { private static final long serialVersionUID 20250627L; // 主键ID // JsonProperty(id) // JSONField(name id) private Long id; // 学生姓名 // JsonProperty(student_name) // JSONField(name student_name) private String studentName; // 学生年龄 // JsonProperty(student_age) // JSONField(name student_age) private Integer studentAge; // 创建时间 // JsonProperty(create_time) // JsonFormat(pattern yyyy-MM-dd) // JsonFormat和JsonFormat 都是Jackson的注解要组合使用 // JSONField(name create_time, format yyyy-MM-dd) private Date createTime; // 最高可接受的辅导价格(价格/小时) // JsonProperty(price) // JSONField(name price) private BigDecimal price; }2.2 不加注释public static void main(String[] args) throws JsonProcessingException { // 不加注释测试 // 1. 创建对象 Student student new Student(); student.setId(10001L); student.setStudentName(张三); student.setStudentAge(20); student.setCreateTime(new Date()); // 当前时间 student.setPrice(new BigDecimal(150.50)); System.out.println(使用对象: student); // 2. 使用FastJson包的方法 序列化为 json 字符串 String studentJsonStr JSON.toJSONString(student); System.out.println(使用FastJson包序列化为Json字符串: studentJsonStr); // 3. 使用FastJson包的方法 反序列化为对象 Student student2 JSON.parseObject(studentJsonStr, Student.class); System.out.println(使用FastJson包反序列化为对象: student2); // 4. 使用Jackson包 方法 序列化为 json 字符串 ObjectMapper objectMapper new ObjectMapper(); String writeValueAsString objectMapper.writeValueAsString(student); System.out.println(使用Jackson包序列化为Json字符串: writeValueAsString); // 5. 使用Jackson包 方法 反序列化为对象 Student student3 objectMapper.readValue(writeValueAsString, Student.class); System.out.println(使用Jackson包反序列化为对象: student3); }运行 main 方法得出下图结果。可以得出在没有加入注解的时候如果将实体类序列化为JSON串时间对象会序列化为时间戳当反序列化时又会把时间戳序列化为 Date 类型对象。此外使用 FastJson 包序列化后属性的顺序似乎发生了变成了从 a~z 的顺序不是原有实体类中定义的顺序而 Jackson 包则保留了原有的类属性顺序在反序列化时则回到了原有的属性顺序一个无用小芝士了解一下。2.3 JsonPropertypublic static void main(String[] args) throws JsonProcessingException { // JsonProperty 注解测试 // 1. 创建对象 Student student new Student(); student.setId(10001L); student.setStudentName(张三); student.setStudentAge(20); student.setCreateTime(new Date()); // 当前时间 student.setPrice(new BigDecimal(150.50)); System.out.println(使用对象: student); // 2. 使用Jackson包 方法 序列化为 json 字符串 ObjectMapper objectMapper new ObjectMapper(); String writeValueAsString objectMapper.writeValueAsString(student); System.out.println(使用Jackson包序列化为Json字符串: writeValueAsString); // 3. 使用Jackson包 方法 反序列化为对象 Student student3 objectMapper.readValue(writeValueAsString, Student.class); System.out.println(使用Jackson包反序列化为对象: student3); }运行 main 方法通过结果不难发现我们添加的注解已经起作用了序列化为 json 的时候变成了下划线并且日期也进行了格式化不再是时间戳。然后我们再来做一步将 json 数据做修改让它重新序列化public static void main(String[] args) throws JsonProcessingException { // JsonProperty 注解测试 // 1. 创建对象 Student student new Student(); student.setId(10001L); student.setStudentName(张三); student.setStudentAge(20); student.setCreateTime(new Date()); // 当前时间 student.setPrice(new BigDecimal(150.50)); System.out.println(使用对象: student); // 2. 使用Jackson包 方法 序列化为 json 字符串 ObjectMapper objectMapper new ObjectMapper(); String writeValueAsString objectMapper.writeValueAsString(student); System.out.println(使用Jackson包序列化为Json字符串: writeValueAsString); // 修改序列化后 json 字符串将 key student_name 改为 studentName去除 key student_age,添加另外一个 key student_sex // 3. 使用Jackson包 方法 反序列化为对象 String json {\id\:10001,\studentName\:\张三\,\student_sex\:\男\,\create_time\:\2025-06-27\,\price\:150.50}; Student student3 objectMapper.readValue(json, Student.class); System.out.println(修改后的json字符串使用Jackson包反序列化为对象: student3); }运行 main 方法可以发现竟然报错了说无法识别 studentName 这个属性因为我们实体类中 studentName 上的注解是 JsonProperty(student_name)与 json 中的 key studentName 不相等没导致无法映射报错。其实想要解决也很简单我们需要在实体类上再添加一个注解注解的意思就是在转化过程中忽略位置的属性(无法正确映射的属性)JsonIgnoreProperties(ignoreUnknown true)然后我们再重新运行方法如下结果所示可以发现因为 studentName 无法映射和 student_age 的缺失导致这两个属性没有反序列化到实体类的属性值中而且多余的键 student_sex 丢失没有映射到实体类中。2.4 JSONField然后我们将实体类的 JSONField 注解打开public static void main(String[] args) throws JsonProcessingException { // JSONField 注解测试 // 1. 创建对象 Student student new Student(); student.setId(10001L); student.setStudentName(张三); student.setStudentAge(20); student.setCreateTime(new Date()); // 当前时间 student.setPrice(new BigDecimal(150.50)); System.out.println(使用对象: student); // 2. 使用FastJson包的方法 序列化为 json 字符串 String studentJsonStr JSON.toJSONString(student); System.out.println(使用FastJson包序列化为Json字符串: studentJsonStr); // 3. 使用FastJson包的方法 反序列化为对象 Student student2 JSON.parseObject(studentJsonStr, Student.class); System.out.println(使用FastJson包反序列化为对象: student2); }运行上述方法从结果可以看出添加了JSONField 注解后。在序列化为 Json 字符串之后key 都变成了我们注解中标注的带有下划线的值了并且日期格式化也生效了没有转化为时间戳而是直接日期。在进行反序列化时带有下划线格式的 Json 通过注解的映射将数据成功反序列化为实体对象。这里我们来多做一步纂改一下 Json 串的数据或者说我们不使用序列化之后的字符串去进行反序列化自定义一个 json 串。public static void main(String[] args) throws JsonProcessingException { // JSONField 注解测试 // 1. 创建对象 Student student new Student(); student.setId(10001L); student.setStudentName(张三); student.setStudentAge(20); student.setCreateTime(new Date()); // 当前时间 student.setPrice(new BigDecimal(150.50)); System.out.println(使用对象: student); // 2. 使用FastJson包的方法 序列化为 json 字符串 String studentJsonStr JSON.toJSONString(student); System.out.println(使用FastJson包序列化为Json字符串: studentJsonStr); // 3. 使用FastJson包的方法 反序列化为对象 // 修改Json 串将 key create_time 改为 createTime将 key student_name 删除添加一个新 key student_sex String json {\createTime\:\2025-06-27\,\id\:10001,\price\:150.50,\student_age\:20,\student_sex\:\男\}; Student student2 JSON.parseObject(json, Student.class); System.out.println(修改后反序列化为对象: student2); }运行方法得出结果不难发现修改1虽然修改了 json 中的 key将create_time 改为 createTime和注解JSONField(name create_time)不同但是 方法还是智能识别1.2.83版本后似乎均有此功能小编没有一个个尝试并进行了映射日期值仍然成功反序列化到了实体类的 createTime 属性上修改2由于去掉了 student_name 这个 key导致反序列化时无值且无映射导致得到的实体对象中 studentName 值为 null修改3虽然在 json 串中添加了 student_sex 这个年龄 key但是实体类中没有相关的字段进行接收所以得到实体类没有这个属性这个 key 实际上会在反序列化时舍弃。三. 全局映射解决方案3.1 fastjson 包fastjson 不是 Spring Boot 默认的JSON处理器所以通常需要在代码中进行配置小编个人建议如果有自定义配置或全局配置可以在 Configuration 配置类中配置参考如下代码Configuration public class FastjsonConfig implements WebMvcConfigurer { Override public void configureMessageConverters(ListHttpMessageConverter? converters) { // 1. 创建 FastJson 消息转换器 FastJsonHttpMessageConverter converter new FastJsonHttpMessageConverter(); // 2. 创建配置类 FastJsonConfig config new FastJsonConfig(); // 全局驼峰转下划线 config.setSerializerFeatures(SerializerFeature.PrettyFormat); config.setDateFormat(yyyy-MM-dd HH:mm:ss); // 3. 将配置注入转换器 converter.setFastJsonConfig(config); // 4. 添加到转换器列表并优先使用 converters.add(converter); } }3.2 jackson 包因为 jcakson 是Spring Boot 默认的JSON处理器所以它的配置支持使用 yml 文件同时也支持Configuration 配置文件。如果 yml 文件和 Configuration 同时配置则 Configuration 优先级更高方式一yml 文件(推荐方案)优点更加灵活无需代码缺点自定义序列化无法实现仍需使用配置文件。spring: jackson: property-naming-strategy: SNAKE_CASE # 驼峰转下划线 date-format: yyyy-MM-dd HH:mm:ss # 日期格式 fail-on-unknown-properties: false # 忽略未知字段 time-zone: GMT8 # 时区方式二配置文件Configuration public class JacksonConfig { Bean public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); mapper.setDateFormat(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss)); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return mapper; } }四. Serializable 接口(多学一招)细心的小伙伴可以发现在绝大多数实体类中都实现了 Serializable 接口并且通常都会定义一个序列化版本号。private static final long serialVersionUID 1L;那么 Serializable 是否和 fastjson、jackson 有什么关联呢这里可以给个问小伙伴解答一下Serializable 接口与后两者没有直接关联。实体类实现 Serializable 接口主要是为了支持Java原生序列化机制而 serialVersionUID 用于版本控制。而且如果需要用到 ObjectInputStream、ObjectOutputStream、HttpSession 存储等操作必须实现 Serializable 接口此外实现Serializable接口 的好处还有以下几点大家可以参考一下版本控制机制兼容新老数据旧数据可被新版本反序列化新版本也可被旧版本反序列化避免隐式UID风险环境不一致可能导致开发/生产环境的JVM差异导致序列化失败若修改类字段类版本变更可能导致反序列化后抛出 InvalidClassException 无效的类异常显式UID的优势场景显式 UID隐式 UID新增非关键字段兼容不兼容修改字段顺序兼容不兼容跨 JVM 供应商部署兼容可能不兼容重构方法不影响字段兼容不兼容总而言之以后各位小伙伴在做需求新建表新建实体类的时候无脑加上 Serializable 接口的实现 和 private static final long serialVersionUID 1L 就行。