@JsonProperty 和 @JSONField注解的使用方法及其异同?

发布于:2025-06-28 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

一. 区别对比

二. 代码举例说明

2.1 准备实体类

2.2 不加注释

2.3 @JsonProperty

2.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属性:
serialize=false(不序列化)
deserialize=false(不反序列化)
日期格式化 需配合@JsonFormat
@JsonFormat(pattern="yyyy-MM-dd")
直接支持:
@JSONField(format="yyyy-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 @JsonProperty
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);

        // 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(List<HttpMessageConverter<?>> 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: GMT+8                      # 时区

方式二:配置文件

@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 就行。


网站公告

今日签到

点亮在社区的每一天
去签到