四时宝库

程序员的知识宝库

Jackson 实战封神!5 大场景 JSON 转换 + 避坑指南(代码可抄)




一、引言:Jackson 为何是 Java 开发者的必修课?

Jackson 作为 Spring Boot 默认 JSON 解析库(含于 jackson-databind 依赖),解决了 Java 对象与 JSON 互转的核心痛点。其核心优势在于:



  1. 高性能:比 Gson 快 30%+,支持流式处理
  2. 灵活性:注解 + 配置双重控制序列化行为
  3. 兼容性:完美支持 Java 8 时间 API(需 jackson-datatype-jsr310)
  4. 线程安全:ObjectMapper 实例可全局复用(Spring 自动注入)



本文聚焦对象 / 字符串 / 集合 / 数组 / Map 转 JSON五大实战场景,每个案例均提供可直接复制的代码,并揭露 90% 开发者踩过的坑。


二、五大核心场景实战(附完整代码)

场景 1:Java 对象 <-> JSON(最常用)

核心 API
ObjectMapper.writeValueAsString()(序列化)、readValue()(反序列化)

实战代码

java

// 1. 定义实体类(含注解控制)
public class User {
    @JsonProperty("user_id") // 自定义JSON字段名
    private Long id;
    private String name;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") // 日期格式化
    private LocalDateTime createTime;
    @JsonIgnore // 排除序列化
    private String password;
    // 必须有无参构造(反序列化必填)
    public User() {}
    // getter/setter省略
}

// 2. 序列化:对象→JSON
@Autowired
private ObjectMapper mapper; // Spring自动注入

public void objectToJson() throws JsonProcessingException {
    User user = new User();
    user.setId(1L);
    user.setName("Jackson");
    user.setCreateTime(LocalDateTime.now());
    user.setPassword("123456"); // 会被@JsonIgnore排除
    
    String json = mapper.writeValueAsString(user);
    // 输出:{"user_id":1,"name":"Jackson","createTime":"2025-09-02 22:30:00"}
    System.out.println(json);
}

// 3. 反序列化:JSON→对象
public void jsonToObject() throws JsonProcessingException {
    String json = "{\"user_id\":1,\"name\":\"Jackson\",\"createTime\":\"2025-09-02 22:30:00\"}";
    User user = mapper.readValue(json, User.class);
    System.out.println(user.getName()); // 输出:Jackson
}

避坑点

  • 反序列化必须提供无参构造(否则报InvalidFormatException)
  • 日期字段务必加timezone="GMT+8",否则默认 UTC 时差 8 小时

场景 2:JSON 字符串 <-> JsonNode(动态解析)

核心 API:readTree()(字符串→JsonNode)、path()(安全取值)
实战代码

java

public void jsonNodeOperation() throws JsonProcessingException {
    String json = "{\"code\":200,\"data\":{\"list\":[{\"id\":1,\"name\":\"Java\"}]}}";
    
    // 1. 字符串转JsonNode
    JsonNode rootNode = mapper.readTree(json);
    
    // 2. 安全取值(避免NullPointerException)
    int code = rootNode.path("code").asInt(); // 200
    String name = rootNode.path("data")
                          .path("list")
                          .path(0)
                          .path("name")
                          .asText(); // Java
    
    // 3. 修改节点值
    ((ObjectNode) rootNode).put("code", 201);
    String modifiedJson = mapper.writeValueAsString(rootNode);
    // 输出:{"code":201,"data":{"list":[{"id":1,"name":"Java"}]}}
}

优势:无需定义实体类,适合接口返回结构动态变化的场景。

场景 3:集合(List/Set) <-> JSON

核心痛点:Java 泛型擦除导致直接反序列化List<User>失败
解决方案:用TypeReference保留泛型信息
实战代码

java

public void collectionToJson() throws JsonProcessingException {
    // 1. List→JSON
    List<User> userList = Arrays.asList(
        new User(1L, "A"), 
        new User(2L, "B")
    );
    String listJson = mapper.writeValueAsString(userList);
    // 输出:[{"user_id":1,"name":"A",...},{"user_id":2,"name":"B",...}]
    
    // 2. JSON→List(关键:用TypeReference)
    List<User> parsedList = mapper.readValue(
        listJson, 
        new TypeReference<List<User>>() {} // 匿名内部类保留泛型
    );
    System.out.println(parsedList.size()); // 输出:2
}

避坑点:严禁用List.class作为readValue的类型参数,会导致元素被解析为LinkedHashMap而非User。

场景 4:数组 <-> JSON

核心 API:直接支持数组类型,无需额外配置
实战代码

java

public void arrayToJson() throws JsonProcessingException {
    // 1. 基本类型数组→JSON
    int[] intArray = {1, 2, 3};
    String intJson = mapper.writeValueAsString(intArray); // [1,2,3]
    
    // 2. 对象数组→JSON
    User[] userArray = {new User(1L, "A"), new User(2L, "B")};
    String userJson = mapper.writeValueAsString(userArray);
    // 输出:[{"user_id":1,"name":"A",...},{"user_id":2,"name":"B",...}]
    
    // 3. JSON→对象数组
    User[] parsedArray = mapper.readValue(userJson, User[].class);
    System.out.println(parsedArray.length); // 输出:2
}

技巧:数组转 List 可结合Arrays.asList(parsedArray),但注意返回的是固定大小的 List。

场景 5:Map <-> JSON(键值对灵活转换)

核心特性:支持HashMap(无序)和LinkedHashMap(有序)
实战代码

java

public void mapToJson() throws JsonProcessingException {
    // 1. LinkedHashMap(保留插入顺序)→JSON
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("id", 1);
    map.put("name", "Jackson");
    map.put("tags", Arrays.asList("JSON", "Java"));
    
    String mapJson = mapper.writeValueAsString(map);
    // 输出:{"id":1,"name":"Jackson","tags":["JSON","Java"]}
    
    // 2. JSON→Map(泛型安全)
    Map<String, Object> parsedMap = mapper.readValue(
        mapJson, 
        new TypeReference<Map<String, Object>>() {}
    );
    List<String> tags = (List<String>) parsedMap.get("tags");
    System.out.println(tags.get(0)); // 输出:JSON
}

避坑点:HashMap序列化后键的顺序不固定,需有序场景务必用LinkedHashMap。


三、核心配置避坑指南(全局 + 局部)

1. 日期格式化(解决时区差问题)

全局配置(application.yml):

yaml

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8 # 关键:避免UTC时差
    serialization:
      write-dates-as-timestamps: false # 禁用时间戳

局部配置(优先级高于全局):

java

@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDate birthday;

2. 空值处理(控制 null 字段是否序列化)

全局配置

java

// 方式1:代码配置
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 排除null字段
// 方式2:yml配置
spring.jackson.default-property-inclusion: non_null

局部配置

java

@JsonInclude(JsonInclude.Include.ALWAYS) // 强制序列化该字段(即使null)
private String remark;

关键区别:手动设为null的字段会被序列化(如user.setRemark(null)),默认未赋值的null字段会被排除。

3. 泛型安全(TypeReference 原理)

TypeReference通过匿名内部类保留泛型信息,避免类型擦除:

java

// 错误写法(泛型丢失,元素为LinkedHashMap)
List<User> wrongList = mapper.readValue(json, List.class);
// 正确写法(泛型保留)
List<User> rightList = mapper.readValue(json, new TypeReference<List<User>>() {});

四、总结与进阶

  1. 核心工具类:ObjectMapper(全局复用)、JsonNode(动态解析)、TypeReference(泛型安全)
  2. 必避 3 大坑:日期时区差:加timezone="GMT+8"泛型擦除:用TypeReference空值失控:全局NON_NULL+ 局部ALWAYS
  3. 进阶扩展:自定义序列化器:实现StdSerializer(如枚举特殊处理)流式处理:JsonGenerator(超大 JSON 文件,内存友好)注解大全:@JsonAlias(多字段映射)、@JsonView(视图过滤)

感谢关注【AI码力】!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接