Jackson详解

文章目录

    • 1. Jackson简介
    • 2. 在Spring Boot项目中引入Jackson
      • 2.1 Maven依赖
      • 2.2 Gradle依赖
      • 2.3 Spring Boot中的Jackson配置
        • 2.3.1 通过application.properties/application.yml配置
        • 2.3.2 通过Java配置类定制ObjectMapper
    • 3. Jackson基本用法
      • 3.1 Java对象转JSON字符串(序列化)
      • 3.2 JSON字符串转Java对象(反序列化)
      • 3.3 处理集合类型
        • 3.3.1 Java集合转JSON数组
        • 3.3.2 JSON数组转Java集合
      • 3.4 处理Map类型
      • 3.5 处理复杂对象
      • 3.6 处理日期和时间
        • 3.6.1 使用java.util.Date
        • 3.6.2 使用Java 8日期时间API
    • 4. Jackson注解的使用
      • 4.1 属性包含和排除注解
        • 4.1.1 @JsonProperty
        • 4.1.2 @JsonIgnore
        • 4.1.3 @JsonIgnoreProperties
        • 4.1.4 @JsonInclude
      • 4.2 序列化控制注解
        • 4.2.1 @JsonFormat
        • 4.2.2 @JsonSerialize
      • 4.3 反序列化控制注解
        • 4.3.1 @JsonDeserialize
        • 4.3.2 @JsonCreator与@JsonProperty结合使用
      • 4.4 其他常用注解
        • 4.4.1 @JsonRootName
        • 4.4.2 @JsonUnwrapped
        • 4.4.3 @JsonView
    • 5. Jackson的高级特性
      • 5.1 自定义序列化器
      • 5.2 自定义反序列化器
      • 5.3 混合注解(MixIn)
      • 5.4 类型转换与多态
        • 5.4.1 @JsonTypeInfo, @JsonSubTypes, @JsonTypeName
      • 5.5 树模型API
    • 6. 在Spring Boot中使用Jackson的实例
      • 6.1 RESTful API的自动序列化/反序列化
      • 6.2 自定义Jackson配置并应用到特定接口
      • 6.3 处理RESTful API中的错误
    • 7. Jackson的性能优化
      • 7.1 重用ObjectMapper
      • 7.2 启用/禁用特性以提高性能
      • 7.3 使用流式API处理大型JSON
    • 8. 安全最佳实践
      • 8.1 防止多态类型绑定漏洞
      • 8.2 避免反序列化不受信任的内容
      • 8.3 使用Jackson的数据验证
    • 9. 常见问题及解决方案
      • 9.1 处理日期和时间格式问题
      • 9.2 处理未知属性问题
      • 9.3 处理空对象和集合
      • 9.4 处理循环引用
    • 10. 与其他JSON库的比较
      • 10.1 Jackson vs Gson
      • 10.2 Jackson vs Fastjson
      • 10.3 选择JSON库的考虑因素
    • 11. 总结与最佳实践
      • 11.1 Jackson的主要优势
      • 11.2 最佳实践建议
      • 10.2 Jackson vs Fastjson
      • 10.3 选择JSON库的考虑因素
    • 11. 总结与最佳实践
      • 11.1 Jackson的主要优势
      • 11.2 最佳实践建议

1. Jackson简介

Jackson是Java生态系统中最受欢迎的JSON处理库之一,由FasterXML维护。它提供了一套全面的工具来处理JSON数据,支持Java对象与JSON数据之间的转换,同时也提供了处理其他数据格式的能力。Jackson具有以下特点:

  • 高性能:经过优化的代码使其成为最快的JSON处理器之一
  • 灵活性:提供多种处理JSON的方式和广泛的定制选项
  • 稳定性:经过广泛测试和使用,非常稳定可靠
  • 可扩展性:提供扩展API,支持自定义序列化和反序列化
  • 无依赖性:核心模块不依赖于其他库
  • 全面的注解支持:大量注解用于控制JSON处理行为
  • 数据绑定:强大的数据绑定能力,可以将JSON直接转换为Java对象

Jackson库被分为多个模块,其核心组件包括:

  • jackson-core:核心功能,提供基本的流式API,包括JsonParser和JsonGenerator
  • jackson-databind:数据绑定功能,用于处理Java对象和JSON之间的转换
  • jackson-annotations:提供标准的Jackson注解集合

2. 在Spring Boot项目中引入Jackson

2.1 Maven依赖

Spring Boot已经包含了Jackson依赖,当您添加spring-boot-starter-web时,Jackson会被自动引入。如果需要单独引入或指定版本,可以在pom.xml中添加:


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>


<dependency>
    <groupId>com.fasterxml.jackson.coregroupId>
    <artifactId>jackson-databindartifactId>
    <version>2.14.0version> 
dependency>


<dependency>
    <groupId>com.fasterxml.jackson.dataformatgroupId>
    <artifactId>jackson-dataformat-xmlartifactId>
    <version>2.14.0version>
dependency>


<dependency>
    <groupId>com.fasterxml.jackson.datatypegroupId>
    <artifactId>jackson-datatype-jsr310artifactId>
    <version>2.14.0version>
dependency>

2.2 Gradle依赖

如果您使用Gradle,可以在build.gradle文件中添加:

// Spring Boot Web依赖,已含Jackson
implementation 'org.springframework.boot:spring-boot-starter-web'

// 或者单独引入Jackson
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0'

// 引入Jackson XML支持(可选)
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'

// 日期时间模块
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0'

2.3 Spring Boot中的Jackson配置

Spring Boot自动配置了Jackson,不需要额外的配置。但如果您想自定义Jackson行为,可以通过以下方式:

2.3.1 通过application.properties/application.yml配置
# 配置日期格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
# 配置时区
spring.jackson.time-zone=Asia/Shanghai
# 在序列化时忽略null值
spring.jackson.default-property-inclusion=non_null
# 允许未知属性
spring.jackson.deserialization.fail-on-unknown-properties=false
# 允许空对象
spring.jackson.serialization.fail-on-empty-beans=false

或者使用YAML格式:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: Asia/Shanghai
    default-property-inclusion: non_null
    deserialization:
      fail-on-unknown-properties: false
    serialization:
      fail-on-empty-beans: false
2.3.2 通过Java配置类定制ObjectMapper
package com.example.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.text.SimpleDateFormat;

@Configuration
public class JacksonConfig {

    @Bean
    @Primary
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        
        // 设置日期格式
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        
        // 设置Java 8日期时间模块
        objectMapper.registerModule(new JavaTimeModule());
        
        // 禁用将日期作为时间戳输出
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        // 禁用在空对象上失败
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        
        // 美化输出
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        
        return objectMapper;
    }
}

3. Jackson基本用法

3.1 Java对象转JSON字符串(序列化)

import com.fasterxml.jackson.databind.ObjectMapper;

// 创建一个简单的用户类
public class User {
    private Long id;
    private String name;
    private int age;
    
    // 构造函数、getter和setter方法
    public User() {}
    
    public User(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    // getter和setter方法...
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

// 使用Jackson将对象转换为JSON字符串
ObjectMapper mapper = new ObjectMapper();
User user = new User(1L, "张三", 25);

String jsonString = mapper.writeValueAsString(user);
System.out.println(jsonString);
// 输出: {"id":1,"name":"张三","age":25}

// 美化输出
String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(prettyJson);
/*
输出:
{
  "id" : 1,
  "name" : "张三",
  "age" : 25
}
*/

3.2 JSON字符串转Java对象(反序列化)

// 定义JSON字符串
String jsonString = "{\"id\":2,\"name\":\"李四\",\"age\":30}";

// 使用Jackson将JSON字符串转换为Java对象
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(jsonString, User.class);

System.out.println(user.getId() + ", " + user.getName() + ", " + user.getAge());
// 输出: 2, 李四, 30

3.3 处理集合类型

3.3.1 Java集合转JSON数组
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;

// 创建用户列表
List<User> userList = new ArrayList<>();
userList.add(new User(1L, "张三", 25));
userList.add(new User(2L, "李四", 30));
userList.add(new User(3L, "王五", 35));

// 序列化列表
ObjectMapper mapper = new ObjectMapper();
String jsonArrayString = mapper.writeValueAsString(userList);

System.out.println(jsonArrayString);
// 输出: [{"id":1,"name":"张三","age":25},{"id":2,"name":"李四","age":30},{"id":3,"name":"王五","age":35}]
3.3.2 JSON数组转Java集合
// 定义JSON数组字符串
String jsonArrayString = "[{\"id\":1,\"name\":\"张三\",\"age\":25},{\"id\":2,\"name\":\"李四\",\"age\":30}]";

// 反序列化为用户列表
ObjectMapper mapper = new ObjectMapper();

// 方法1:使用TypeReference
import com.fasterxml.jackson.core.type.TypeReference;
List<User> userList1 = mapper.readValue(jsonArrayString, new TypeReference<List<User>>() {});

// 方法2:使用CollectionType
import com.fasterxml.jackson.databind.type.CollectionType;
CollectionType listType = 
    mapper.getTypeFactory().constructCollectionType(List.class, User.class);
List<User> userList2 = mapper.readValue(jsonArrayString, listType);

// 遍历用户列表
for (User user : userList1) {
    System.out.println(user.getId() + ", " + user.getName() + ", " + user.getAge());
}

3.4 处理Map类型

import java.util.HashMap;
import java.util.Map;

// Map转JSON
Map<String, Object> map = new HashMap<>();
map.put("id", 1);
map.put("name", "张三");
map.put("active", true);
map.put("scores", new int[] {90, 85, 78});

ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(map);
System.out.println(jsonString);
// 输出: {"name":"张三","id":1,"active":true,"scores":[90,85,78]}

// JSON转Map
String jsonMapString = "{\"name\":\"李四\",\"id\":2,\"active\":false,\"address\":{\"city\":\"北京\",\"street\":\"朝阳路\"}}";
Map<String, Object> resultMap = mapper.readValue(jsonMapString, new TypeReference<Map<String, Object>>() {});

System.out.println("name: " + resultMap.get("name"));
System.out.println("id: " + resultMap.get("id"));
System.out.println("active: " + resultMap.get("active"));

// 处理嵌套对象
@SuppressWarnings("unchecked")
Map<String, String> address = (Map<String, String>) resultMap.get("address");
System.out.println("city: " + address.get("city"));
System.out.println("street: " + address.get("street"));

3.5 处理复杂对象

// 定义部门类
public class Department {
    private Long id;
    private String name;
    private List<User> users;
    
    // 构造函数、getter和setter方法
    public Department() {}
    
    public Department(Long id, String name, List<User> users) {
        this.id = id;
        this.name = name;
        this.users = users;
    }
    
    // getter和setter方法...
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public List<User> getUsers() { return users; }
    public void setUsers(List<User> users) { this.users = users; }
}

// 创建复杂对象
List<User> users = new ArrayList<>();
users.add(new User(1L, "张三", 25));
users.add(new User(2L, "李四", 30));

Department department = new Department(1L, "技术部", users);

// 序列化复杂对象
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(department);
System.out.println(jsonString);
// 输出: {"id":1,"name":"技术部","users":[{"id":1,"name":"张三","age":25},{"id":2,"name":"李四","age":30}]}

// 反序列化复杂对象
Department parsedDepartment = mapper.readValue(jsonString, Department.class);
System.out.println("部门名称: " + parsedDepartment.getName());
System.out.println("用户数量: " + parsedDepartment.getUsers().size());

3.6 处理日期和时间

3.6.1 使用java.util.Date
import java.util.Date;
import java.text.SimpleDateFormat;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Event {
    private Long id;
    private String name;
    private Date eventDate;
    
    // 构造函数、getter和setter方法
    // ...
}

// 创建带日期的对象
Event event = new Event();
event.setId(1L);
event.setName("年会");
event.setEventDate(new Date());

// 配置日期格式
ObjectMapper mapper = new ObjectMapper();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mapper.setDateFormat(dateFormat);

// 序列化
String jsonString = mapper.writeValueAsString(event);
System.out.println(jsonString);
// 输出: {"id":1,"name":"年会","eventDate":"2023-06-15 14:30:45"}

// 反序列化
Event parsedEvent = mapper.readValue(jsonString, Event.class);
System.out.println("事件名称: " + parsedEvent.getName());
System.out.println("事件日期: " + dateFormat.format(parsedEvent.getEventDate()));
3.6.2 使用Java 8日期时间API
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.SerializationFeature;

public class ModernEvent {
    private Long id;
    private String name;
    private LocalDate eventDate;
    private LocalDateTime eventDateTime;
    
    // 构造函数、getter和setter方法
    // ...
}

// 创建带Java 8日期时间的对象
ModernEvent event = new ModernEvent();
event.setId(1L);
event.setName("产品发布会");
event.setEventDate(LocalDate.of(2023, 6, 15));
event.setEventDateTime(LocalDateTime.of(2023, 6, 15, 14, 30, 0));

// 配置ObjectMapper支持Java 8日期时间
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

// 序列化
String jsonString = mapper.writeValueAsString(event);
System.out.println(jsonString);
// 输出: {"id":1,"name":"产品发布会","eventDate":"2023-06-15","eventDateTime":"2023-06-15T14:30:00"}

// 反序列化
ModernEvent parsedEvent = mapper.readValue(jsonString, ModernEvent.class);
System.out.println("事件名称: " + parsedEvent.getName());
System.out.println("事件日期: " + parsedEvent.getEventDate());
System.out.println("事件时间: " + parsedEvent.getEventDateTime());

4. Jackson注解的使用

Jackson提供了丰富的注解系统,可以精确控制序列化和反序列化的行为。以下是常用的注解:

4.1 属性包含和排除注解

4.1.1 @JsonProperty

用于指定JSON属性名称,可以与Java字段名不同:

public class User {
    @JsonProperty("user_id")
    private Long id;
    
    @JsonProperty("user_name")
    private String name;
    
    // 其他字段和方法...
}

// 序列化结果: {"user_id":1,"user_name":"张三","age":25}
4.1.2 @JsonIgnore

用于排除不需要序列化/反序列化的字段:

public class User {
    private Long id;
    private String name;
    
    @JsonIgnore
    private String password; // 不会被序列化/反序列化
    
    private int age;
    
    // 其他字段和方法...
}
4.1.3 @JsonIgnoreProperties

类级别注解,可以一次性指定多个要忽略的属性:

@JsonIgnoreProperties({"password", "salary", "emailAddress"})
public class User {
    private Long id;
    private String name;
    private String password;   // 将被忽略
    private double salary;     // 将被忽略
    private String emailAddress; // 将被忽略
    private int age;
    
    // 其他字段和方法...
}
4.1.4 @JsonInclude

控制在何种条件下包含属性:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

// 类级别设置
@JsonInclude(Include.NON_NULL)
public class User {
    private Long id;
    private String name;
    private Integer age; // 如果为null,将不会被序列化
    
    // 属性级别设置,覆盖类级别
    @JsonInclude(Include.NON_EMPTY)
    private List<String> roles; // 如果为空列表,将不会被序列化
    
    // 其他字段和方法...
}

常用的Include选项有:

  • Include.ALWAYS:始终包含属性,不管值是什么(默认行为)
  • Include.NON_NULL:只包含非null值
  • Include.NON_EMPTY:排除null或空集合/数组/字符串
  • Include.NON_DEFAULT:排除默认值(如数字0,布尔值false)
  • Include.NON_ABSENT:排除null和缺失值(Optional.empty())

4.2 序列化控制注解

4.2.1 @JsonFormat

用于指定日期/时间格式:

public class Event {
    private Long id;
    private String name;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date eventDate;
    
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate eventDay;
    
    // 其他字段和方法...
}
4.2.2 @JsonSerialize

使用自定义的序列化器:

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

public class Product {
    @JsonSerialize(using = ToStringSerializer.class)
    private BigDecimal price; // 将使用toString()方法进行序列化
    
    // 其他字段和方法...
}

4.3 反序列化控制注解

4.3.1 @JsonDeserialize

使用自定义的反序列化器:

import com.fasterxml.jackson.databind.deser.std.DateDeserializers.DateDeserializer;

public class Event {
    private Long id;
    private String name;
    
    @JsonDeserialize(using = DateDeserializer.class)
    private Date eventDate;
    
    // 其他字段和方法...
}
4.3.2 @JsonCreator与@JsonProperty结合使用

当JSON不能直接匹配构造函数或工厂方法的参数时,可以使用这种组合:

public class User {
    private final Long id;
    private final String name;
    private final int age;
    
    // 使用@JsonCreator指定用于反序列化的构造函数
    @JsonCreator
    public User(
        @JsonProperty("user_id") Long id, 
        @JsonProperty("user_name") String name, 
        @JsonProperty("user_age") int age) {
        
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    // 只有getter方法,没有setter方法
    public Long getId() { return id; }
    public String getName() { return name; }
    public int getAge() { return age; }
}

// 使用这种方式可以反序列化以下JSON
// {"user_id":1,"user_name":"张三","user_age":25}

4.4 其他常用注解

4.4.1 @JsonRootName

用于指定序列化时的根元素名称,需要启用SerializationFeature.WRAP_ROOT_VALUE

@JsonRootName("user")
public class User {
    private Long id;
    private String name;
    private int age;
    
    // 其他字段和方法...
}

// 配置启用根包装
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String json = mapper.writeValueAsString(new User(1L, "张三", 25));

// 输出: {"user":{"id":1,"name":"张三","age":25}}
4.4.2 @JsonUnwrapped

用于在序列化/反序列化时展开嵌套对象:

public class Address {
    private String city;
    private String street;
    
    // getter和setter方法...
}

public class User {
    private Long id;
    private String name;
    
    @JsonUnwrapped
    private Address address;
    
    // 其他字段和方法...
}

// 创建对象
User user = new User();
user.setId(1L);
user.setName("张三");
Address address = new Address();
address.setCity("北京");
address.setStreet("朝阳路");
user.setAddress(address);

// 序列化
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);

// 输出: {"id":1,"name":"张三","city":"北京","street":"朝阳路"}
// 注意address的属性被展开到顶层
4.4.3 @JsonView

用于控制对象的部分视图,实现同一个对象的不同JSON表示:

// 定义视图
public class Views {
    public static class Public { }
    public static class Internal extends Public { }
    public static class Admin extends Internal { }
}

public class User {
    @JsonView(Views.Public.class)
    private Long id;
    
    @JsonView(Views.Public.class)
    private String name;
    
    @JsonView(Views.Internal.class)
    private String email;
    
    @JsonView(Views.Admin.class)
    private String password;
    
    // 其他字段和方法...
}

// 使用视图进行序列化
ObjectMapper mapper = new ObjectMapper();
User user = new User(1L, "张三", "[email protected]", "password123");

// 公开视图
String publicJson = mapper.writerWithView(Views.Public.class)
    .writeValueAsString(user);
// 输出: {"id":1,"name":"张三"}

// 内部视图
String internalJson = mapper.writerWithView(Views.Internal.class)
    .writeValueAsString(user);
// 输出: {"id":1,"name":"张三","email":"[email protected]"}

// 管理员视图
String adminJson = mapper.writerWithView(Views.Admin.class)
    .writeValueAsString(user);
// 输出: {"id":1,"name":"张三","email":"[email protected]","password":"password123"}

5. Jackson的高级特性

5.1 自定义序列化器

当需要定制序列化逻辑时,可以编写自定义序列化器:

import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

// 自定义序列化器
public class MoneySerializer extends JsonSerializer<BigDecimal> {
    private final DecimalFormat formatter = new DecimalFormat("¥#,##0.00");
    
    @Override
    public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) 
            throws IOException {
        gen.writeString(formatter.format(value));
    }
}

// 在类中使用自定义序列化器
public class Product {
    private Long id;
    private String name;
    
    @JsonSerialize(using = MoneySerializer.class)
    private BigDecimal price;
    
    // 其他字段和方法...
}

// 序列化示例
Product product = new Product();
product.setId(1L);
product.setName("笔记本电脑");
product.setPrice(new BigDecimal("6999.99"));

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(product);
// 输出: {"id":1,"name":"笔记本电脑","price":"¥6,999.99"}

5.2 自定义反序列化器

同样,可以编写自定义反序列化器:

import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

// 自定义反序列化器
public class MoneyDeserializer extends JsonDeserializer<BigDecimal> {
    private final DecimalFormat formatter = new DecimalFormat("¥#,##0.00");
    
    @Override
    public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) 
            throws IOException {
        String valueAsString = p.getValueAsString();
        try {
            // 移除货币符号和逗号
            String normalized = valueAsString.replace("¥", "").replace(",", "");
            return new BigDecimal(normalized);
        } catch (NumberFormatException e) {
            throw new IOException("无法解析金额: " + valueAsString, e);
        }
    }
}

// 在类中使用自定义反序列化器
public class Product {
    private Long id;
    private String name;
    
    @JsonDeserialize(using = MoneyDeserializer.class)
    private BigDecimal price;
    
    // 其他字段和方法...
}

5.3 混合注解(MixIn)

当您无法修改要序列化/反序列化的类时,可以使用混合注解:

// 无法修改的第三方类
public class ThirdPartyUser {
    private Long id;
    private String name;
    private String email;
    private String internalData; // 不希望序列化此字段
    
    // getter和setter方法...
}

// 创建混合注解接口
@JsonIgnoreProperties({"internalData"})
abstract class UserMixIn {
    @JsonProperty("user_id")
    abstract Long getId();
    
    @JsonProperty("user_name")
    abstract String getName();
}

// 将混合注解应用到ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(ThirdPartyUser.class, UserMixIn.class);

// 序列化
ThirdPartyUser user = new ThirdPartyUser();
user.setId(1L);
user.setName("张三");
user.setEmail("[email protected]");
user.setInternalData("私有数据");

String json = mapper.writeValueAsString(user);
// 输出: {"user_id":1,"user_name":"张三","email":"[email protected]"}
// 注意:internalData被忽略,id和name属性名被改变

5.4 类型转换与多态

5.4.1 @JsonTypeInfo, @JsonSubTypes, @JsonTypeName

用于处理多态情况,如继承关系:

// 基类
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal {
    private String name;
    
    // getter和setter方法...
}

// 子类
@JsonTypeName("dog")
public class Dog extends Animal {
    private String barkSound;
    
    // getter和setter方法...
}

@JsonTypeName("cat")
public class Cat extends Animal {
    private int livesLeft;
    
    // getter和setter方法...
}

// 使用示例
ObjectMapper mapper = new ObjectMapper();

// 创建动物列表
List<Animal> animals = new ArrayList<>();
Dog dog = new Dog();
dog.setName("旺财");
dog.setBarkSound("汪汪");
animals.add(dog);

Cat cat = new Cat();
cat.setName("咪咪");
cat.setLivesLeft(9);
animals.add(cat);

// 序列化
String json = mapper.writeValueAsString(animals);
/*
输出:
[
  {"type":"dog","name":"旺财","barkSound":"汪汪"},
  {"type":"cat","name":"咪咪","livesLeft":9}
]
*/

// 反序列化
List<Animal> deserializedAnimals = mapper.readValue(json, 
    new TypeReference<List<Animal>>() {});

// 验证对象类型
System.out.println(deserializedAnimals.get(0).getClass()); // class Dog
System.out.println(deserializedAnimals.get(1).getClass()); // class Cat

5.5 树模型API

除了对象绑定API外,Jackson还提供了树模型API,适用于动态操作JSON:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

// 读取JSON到树模型
String jsonString = "{\"name\":\"张三\",\"age\":25,\"address\":{\"city\":\"北京\",\"street\":\"朝阳路\"},\"hobbies\":[\"阅读\",\"游泳\"]}";
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonString);

// 访问属性
String name = rootNode.get("name").asText();
int age = rootNode.get("age").asInt();
String city = rootNode.get("address").get("city").asText();

// 遍历数组
ArrayNode hobbies = (ArrayNode) rootNode.get("hobbies");
for (JsonNode hobby : hobbies) {
    System.out.println("爱好: " + hobby.asText());
}

// 创建新的树模型
ObjectNode userNode = mapper.createObjectNode();
userNode.put("name", "李四");
userNode.put("age", 30);

ObjectNode addressNode = mapper.createObjectNode();
addressNode.put("city", "上海");
addressNode.put("street", "南京路");
userNode.set("address", addressNode);

ArrayNode newHobbies = mapper.createArrayNode();
newHobbies.add("篮球");
newHobbies.add("编程");
userNode.set("hobbies", newHobbies);

// 转换为JSON字符串
String newJsonString = mapper.writeValueAsString(userNode);
System.out.println(newJsonString);

6. 在Spring Boot中使用Jackson的实例

6.1 RESTful API的自动序列化/反序列化

在Spring Boot中,当您返回一个对象或接收一个@RequestBody参数时,Jackson会自动处理序列化和反序列化:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    // 返回对象自动序列化为JSON
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }

    // 返回集合自动序列化为JSON数组
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }

    // 接收JSON请求体并自动反序列化为User对象
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }

    // 返回ResponseEntity
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        User updatedUser = userService.update(user);
        return ResponseEntity.ok(updatedUser);
    }
}

6.2 自定义Jackson配置并应用到特定接口

可以创建不同的ObjectMapper实例,用于特定的序列化需求:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;
    private final ObjectMapper defaultMapper;
    private final ObjectMapper customMapper;

    @Autowired
    public ProductController(ProductService productService, ObjectMapper defaultMapper) {
        this.productService = productService;
        this.defaultMapper = defaultMapper;
        
        // 创建自定义的ObjectMapper
        this.customMapper = new ObjectMapper();
        customMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        customMapper.registerModule(new JavaTimeModule());
        customMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        customMapper.setDateFormat(new SimpleDateFormat("yyyy年MM月dd日"));
    }

    // 使用默认的ObjectMapper(自动配置)
    @GetMapping("/{id}")
    public Product getProductById(@PathVariable Long id) {
        return productService.findById(id);
    }

    // 手动使用自定义ObjectMapper
    @GetMapping("/{id}/custom")
    public String getProductByIdCustomFormat(@PathVariable Long id) throws JsonProcessingException {
        Product product = productService.findById(id);
        return customMapper.writeValueAsString(product);
    }
}

6.3 处理RESTful API中的错误

使用Jackson序列化错误响应:

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理资源未找到异常
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Map<String, Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("timestamp", new Date());
        errorResponse.put("status", HttpStatus.NOT_FOUND.value());
        errorResponse.put("error", "Not Found");
        errorResponse.put("message", ex.getMessage());
        errorResponse.put("path", getCurrentRequestPath());
        return errorResponse;
    }

    // 处理验证异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("timestamp", new Date());
        errorResponse.put("status", HttpStatus.BAD_REQUEST.value());
        errorResponse.put("error", "Validation Failed");
        
        // 收集所有验证错误
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage()));
        
        errorResponse.put("errors", errors);
        errorResponse.put("path", getCurrentRequestPath());
        return errorResponse;
    }

    private String getCurrentRequestPath() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getRequest().getRequestURI();
        } catch (Exception e) {
            return "unknown";
        }
    }
}

7. Jackson的性能优化

7.1 重用ObjectMapper

ObjectMapper的创建成本较高,应该重用实例:

// 在Spring Boot应用中定义ObjectMapper Bean
@Configuration
public class JacksonConfig {

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        // 配置mapper...
        return mapper;
    }
}

// 在服务类中注入和重用
@Service
public class JsonService {

    private final ObjectMapper mapper;

    @Autowired
    public JsonService(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    public String toJson(Object object) throws JsonProcessingException {
        return mapper.writeValueAsString(object);
    }

    public <T> T fromJson(String json, Class<T> clazz) throws JsonProcessingException {
        return mapper.readValue(json, clazz);
    }
}

7.2 启用/禁用特性以提高性能

ObjectMapper mapper = new ObjectMapper();

// 禁用不需要的特性可以提高性能
mapper.disable(MapperFeature.USE_ANNOTATIONS);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

// 对于特定应用,可以进一步优化
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
mapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);

7.3 使用流式API处理大型JSON

对于非常大的JSON文件,使用流式API可以减少内存使用:

// 流式读取大型JSON数组
public void processLargeJsonArray(InputStream jsonStream) throws IOException {
    JsonFactory factory = new JsonFactory();
    JsonParser parser = factory.createParser(jsonStream);
    
    // 确保开始于数组
    if (parser.nextToken() != JsonToken.START_ARRAY) {
        throw new IllegalStateException("预期JSON数组");
    }
    
    // 逐个处理数组元素
    while (parser.nextToken() != JsonToken.END_ARRAY) {
        // 当前元素是一个对象
        if (parser.currentToken() == JsonToken.START_OBJECT) {
            // 处理单个用户对象
            processUserObject(parser);
        }
    }
    
    parser.close();
}

private void processUserObject(JsonParser parser) throws IOException {
    String fieldName;
    Long id = null;
    String name = null;
    
    // 解析对象字段
    while (parser.nextToken() != JsonToken.END_OBJECT) {
        fieldName = parser.getCurrentName();
        parser.nextToken(); // 移动到字段值
        
        if ("id".equals(fieldName)) {
            id = parser.getLongValue();
        } else if ("name".equals(fieldName)) {
            name = parser.getText();
        } else {
            // 忽略其他字段,跳过其值
            parser.skipChildren();
        }
    }
    
    // 处理提取的数据
    if (id != null && name != null) {
        System.out.println("处理用户: " + id + ", " + name);
    }
}

8. 安全最佳实践

8.1 防止多态类型绑定漏洞

Jackson的多态类型绑定可能导致安全漏洞,建议禁用默认类型处理:

ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(
    mapper.getPolymorphicTypeValidator(),
    ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT
);

或者指定可信任的类:

mapper.activateDefaultTyping(
    BasicPolymorphicTypeValidator.builder()
        .allowIfSubType("com.example.model.")
        .build(),
    ObjectMapper.DefaultTyping.NON_FINAL
);

8.2 避免反序列化不受信任的内容

当处理不受信任的输入时,使用安全的配置:

ObjectMapper mapper = new ObjectMapper();
// 禁用自动类型检测
mapper.disable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// 禁用POJO转换为树模型
mapper.disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
// 在未知属性上失败
mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 在数字类型溢出时失败
mapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);

8.3 使用Jackson的数据验证

Jackson可以与Bean Validation一起使用,确保数据符合要求:

import javax.validation.constraints.*;

public class User {
    @NotNull
    private Long id;
    
    @NotBlank
    @Size(min = 2, max = 50)
    private String name;
    
    @Min(0)
    @Max(150)
    private int age;
    
    @Email
    private String email;
    
    // getter和setter方法...
}

// 在Controller中进行验证
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
    // 由于@Valid注解,如果验证失败会抛出MethodArgumentNotValidException
    return ResponseEntity.ok(userService.save(user));
}

9. 常见问题及解决方案

9.1 处理日期和时间格式问题

// 全局配置
@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        
        // 注册模块以支持Java 8日期时间类型
        mapper.registerModule(new JavaTimeModule());
        
        // 配置日期时间格式
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        mapper.setDateFormat(dateFormat);
        
        // 以ISO-8601输出日期时间
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        // 设置时区
        mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        
        return mapper;
    }
}

9.2 处理未知属性问题

// 方法1:通过配置
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

// 方法2:通过注解
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
    // ...
}

9.3 处理空对象和集合

// 方法1:配置不要在空对象上失败
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

// 方法2:全局配置输出null值
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);

// 方法3:全局配置不输出null值
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

9.4 处理循环引用

// 方法1:使用@JsonManagedReference和@JsonBackReference
public class Department {
    @JsonManagedReference
    private List<Employee> employees;
    // ...
}

public class Employee {
    @JsonBackReference
    private Department department;
    // ...
}

// 方法2:直接禁用循环引用检测
mapper.disable(SerializationFeature.FAIL_ON_SELF_REFERENCES);

10. 与其他JSON库的比较

10.1 Jackson vs Gson

Jackson通常具有更高的性能,特别是在处理大型对象时,但Gson配置更简单:

// Jackson示例
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(jsonString, User.class);
String newJsonString = mapper.writeValueAsString(user);

// Gson示例
Gson gson = new Gson();
User user = gson.fromJson(jsonString, User.class);
String newJsonString = gson.toJson(user);

10.2 Jackson vs Fastjson

Fastjson性能更好,但Jackson在功能和生态系统上更完善:

// Jackson示例
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(jsonString, User.class);
String newJsonString = mapper.writeValueAsString(user);

// Fastjson示例
User user = JSON.parseObject(jsonString, User.class);
String newJsonString = JSON.toJSONString(user);

10.3 选择JSON库的考虑因素

  • 性能需求:如果性能是首要考虑因素,可能选择Fastjson
  • 稳定性需求:如果稳定性和维护是关键,选择Jackson
  • 生态系统:如果与Spring Boot集成,Jackson是自然选择
  • 特性需求:Jackson提供了最全面的功能集
  • 学习曲线:Gson更简单,Jackson更复杂但功能更强大

11. 总结与最佳实践

11.1 Jackson的主要优势

  • 与Spring Boot自然集成
  • 高度可定制性
  • 丰富的注解系统
  • 全面的功能支持
  • 活跃的社区和良好的文档

11.2 最佳实践建议

  • 重用ObjectMapper实例
  • 优先使用注解而非编程配置
  • 对于性能关键应用,小心配置特性
  • 遵循安全最佳实践,特别是处理外部输入时
  • 利用Spring Boot的自动配置
  • 对于复杂需求,考虑使用MixIn和自定义序列化器
  • 为所有的Java bean提供默认构造函数和合适的getter/setter
  • 正确处理日期和时间格式
  • 使用JavaTimeModule来支持Java 8日期时间类型
  • 通过单元测试验证复杂的序列化/反序列化逻辑
    izationFeature.FAIL_ON_SELF_REFERENCES);

## 10. 与其他JSON库的比较

### 10.1 Jackson vs Gson

Jackson通常具有更高的性能,特别是在处理大型对象时,但Gson配置更简单:

```java
// Jackson示例
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(jsonString, User.class);
String newJsonString = mapper.writeValueAsString(user);

// Gson示例
Gson gson = new Gson();
User user = gson.fromJson(jsonString, User.class);
String newJsonString = gson.toJson(user);

10.2 Jackson vs Fastjson

Fastjson性能更好,但Jackson在功能和生态系统上更完善:

// Jackson示例
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(jsonString, User.class);
String newJsonString = mapper.writeValueAsString(user);

// Fastjson示例
User user = JSON.parseObject(jsonString, User.class);
String newJsonString = JSON.toJSONString(user);

10.3 选择JSON库的考虑因素

  • 性能需求:如果性能是首要考虑因素,可能选择Fastjson
  • 稳定性需求:如果稳定性和维护是关键,选择Jackson
  • 生态系统:如果与Spring Boot集成,Jackson是自然选择
  • 特性需求:Jackson提供了最全面的功能集
  • 学习曲线:Gson更简单,Jackson更复杂但功能更强大

11. 总结与最佳实践

11.1 Jackson的主要优势

  • 与Spring Boot自然集成
  • 高度可定制性
  • 丰富的注解系统
  • 全面的功能支持
  • 活跃的社区和良好的文档

11.2 最佳实践建议

  • 重用ObjectMapper实例
  • 优先使用注解而非编程配置
  • 对于性能关键应用,小心配置特性
  • 遵循安全最佳实践,特别是处理外部输入时
  • 利用Spring Boot的自动配置
  • 对于复杂需求,考虑使用MixIn和自定义序列化器
  • 为所有的Java bean提供默认构造函数和合适的getter/setter
  • 正确处理日期和时间格式
  • 使用JavaTimeModule来支持Java 8日期时间类型
  • 通过单元测试验证复杂的序列化/反序列化逻辑

你可能感兴趣的:(java,json,spring,boot)