轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

    • 经验

    • JVM

    • 企业应用

      • Freemarker模板实战指南
      • Servlet与JSP指南
      • Java日志系统
      • Java JSON处理
        • 前言
          • JSON的核心特点
          • JSON的数据类型
          • JSON示例
        • Java中的JSON应用场景
          • 1. 数据交换
          • 2. Web服务与RESTful API
          • 3. 数据存储
          • 4. 微服务通信
          • 5. 配置管理
          • 6. 测试数据
        • Java中的JSON库概述
          • 主流JSON库对比
          • Jackson的核心优势
          • 其他主流JSON库简介
          • Gson
          • Fastjson2
        • Jackson的使用
          • 1. 添加Jackson依赖
          • 2. 创建和配置ObjectMapper
          • 基础创建
          • 最佳实践:单例模式
          • 3. JSON与Java对象的相互转换
          • 示例类定义
          • 4. JSON与Java集合的相互转换
          • 5. 复杂对象的处理
          • 6. JsonNode的用法
          • 7. 美化JSON输出
          • 8. JSON处理的行为和特性配置
          • 9. 时间处理
          • 配置ObjectMapper日期格式
          • 使用@JsonFormat格式化日期
          • 自定义日期序列化器
          • 使用Jackson序列化Joda-Time
          • 使用Jackson序列化Java 8日期
          • 10. 自定义序列化和反序列化
          • Jackson的datatype模块
          • 注册自己的Module
          • 11. Jackson的注解
          • 12. 对其他数据格式的支持
          • 13. 其他
        • 性能优化建议
          • 1. ObjectMapper复用
          • 2. 使用@JsonProperty减少反射
          • 3. 避免使用JsonNode进行大量数据处理
          • 4. 合理配置Feature
        • 常见问题与解决方案
          • 1. 循环引用
          • 2. 日期格式问题
          • 3. 大文件处理
        • 总结
      • Java XML处理
      • Java对象池技术
      • Java程序的单元测试(Junit)
      • Thymeleaf官方文档(中文版)
      • Mockito应用
      • Java中的空安全
  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 企业应用
轩辕李
2023-06-06
目录

Java JSON处理

# 前言

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它以易于阅读和编写的文本格式表示结构化数据。

JSON最初由Douglas Crockford在2001年提出,并且广泛应用于Web应用程序之间的数据交换。

# JSON的核心特点

  1. 简洁性:JSON使用简单的文本格式,易于阅读和编写
  2. 可读性:JSON的结构对开发人员来说非常直观和易于理解
  3. 跨平台支持:作为通用的数据交换格式,可以在不同平台和编程语言之间进行交换和解析
  4. 语言无关性:JSON与特定的编程语言无关,可以在多种编程语言中使用
  5. 扩展性:支持嵌套和复杂的数据结构,可以表示大规模的数据
  6. Web友好:与JavaScript密切相关,在Web应用程序中广泛使用,特别是RESTful API

# JSON的数据类型

JSON支持以下基本数据类型:

  • 字符串(String):用双引号包围的文本
  • 数字(Number):整数或浮点数
  • 布尔值(Boolean):true 或 false
  • 数组(Array):有序的值列表,用方括号 [] 表示
  • 对象(Object):键值对集合,用花括号 {} 表示
  • 空值(Null):null

# JSON示例

下面是一个完整的JSON对象示例,展示了各种数据类型的使用:

{
  "name": "John",                    // 字符串类型
  "age": 30,                         // 数字类型
  "city": "New York",                // 字符串类型
  "hobbies": [                       // 数组类型
    "reading", 
    "coding", 
    "traveling"
  ],
  "isStudent": false,                // 布尔类型
  "address": {                       // 嵌套对象
    "street": "123 Main St",
    "zip": "10001"
  },
  "spouse": null                     // 空值类型
}

# Java中的JSON应用场景

在Java开发中,JSON的使用场景非常广泛,主要包括:

# 1. 数据交换

JSON作为通用的数据交换格式,可以在不同系统、不同编程语言之间进行数据交换。在Java中,可以使用JSON来序列化Java对象为JSON格式,并将其发送给其他系统或服务。

# 2. Web服务与RESTful API

  • Spring MVC:默认使用Jackson进行JSON处理
  • JAX-RS:支持多种JSON处理库
  • Spring Boot:自动配置JSON序列化/反序列化

# 3. 数据存储

  • NoSQL数据库:如MongoDB、Elasticsearch等原生支持JSON
  • 关系型数据库:PostgreSQL、MySQL 5.7+支持JSON数据类型
  • 文件存储:将配置或数据以JSON格式保存

# 4. 微服务通信

  • 服务间调用:使用JSON作为消息格式
  • 消息队列:RabbitMQ、Kafka等消息中间件的消息格式
  • 服务注册与发现:配置信息的传递

# 5. 配置管理

  • 应用配置:Spring Boot的application.json
  • 动态配置:配置中心(如Apollo、Nacos)使用JSON格式

# 6. 测试数据

  • Mock数据:模拟外部服务响应
  • 测试用例:定义测试输入和期望输出
  • 接口文档:Swagger/OpenAPI规范使用JSON描述API

# Java中的JSON库概述

在Java生态系统中,有多个成熟的JSON处理库可供选择:

# 主流JSON库对比

特性 Jackson Gson Fastjson2
性能 优秀 良好 最快
功能丰富度 最全面 适中 丰富
社区支持 最活跃 活跃 活跃
Spring集成 默认支持 需配置 需配置
安全性 优秀 优秀 已改进
学习曲线 适中 简单 简单

# Jackson的核心优势

Spring框架默认选择Jackson作为JSON处理库,主要基于以下优势:

  1. 卓越的性能

    • 高效的流式API(Streaming API)
    • 优化的内存使用
    • 快速的序列化/反序列化速度
  2. 强大的功能特性

    • 支持复杂的类型处理(泛型、多态)
    • 灵活的自定义序列化器/反序列化器
    • 完善的注解体系
  3. 多格式支持

    • JSON(默认)
    • XML(jackson-dataformat-xml)
    • YAML(jackson-dataformat-yaml)
    • CSV(jackson-dataformat-csv)
    • Properties(jackson-dataformat-properties)
    • CBOR、Smile等二进制格式
  4. 模块化架构

    • jackson-core:核心流处理
    • jackson-databind:数据绑定
    • jackson-annotations:注解支持
    • 丰富的扩展模块
  5. 企业级特性

    • 线程安全的ObjectMapper
    • 完善的异常处理
    • 详细的文档和社区支持

本篇文章主要介绍Jackson的使用,同时简要介绍其他主流库。

# 其他主流JSON库简介

# Gson

Gson是Google开发的JSON处理库,以简单易用著称:

  • 优点:API简洁、学习成本低、适合简单场景
  • 缺点:性能相对较弱、功能不如Jackson丰富
  • 使用场景:Android开发、简单的JSON处理需求
  • 参考文档:Gson用户指南 (opens new window)

# Fastjson2

Fastjson2是阿里巴巴开源的高性能JSON库:

  • 优点:极致的性能优化、API简单、中文文档丰富
  • 改进:相比Fastjson 1.x,安全性大幅提升
  • 使用场景:高性能要求场景、Dubbo 3.2+默认使用
  • 性能对比:Fastjson2 Benchmark (opens new window)
  • 参考文档:Fastjson2官方文档 (opens new window)

# Jackson的使用

# 1. 添加Jackson依赖

首先,我们需要将Jackson库添加到项目的依赖中。可以在项目的构建文件中添加以下依赖:

<!-- Maven 依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

# 2. 创建和配置ObjectMapper

ObjectMapper是Jackson的核心类,负责JSON的序列化和反序列化操作。

# 基础创建

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper objectMapper = new ObjectMapper();

# 最佳实践:单例模式

ObjectMapper是线程安全的重量级对象,建议使用单例模式:

public class JsonUtils {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    
    static {
        // 配置ObjectMapper
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        // 注册模块
        MAPPER.registerModule(new JavaTimeModule());
    }
    
    public static ObjectMapper getMapper() {
        return MAPPER;
    }
    
    // 工具方法
    public static String toJson(Object obj) throws JsonProcessingException {
        return MAPPER.writeValueAsString(obj);
    }
    
    public static <T> T fromJson(String json, Class<T> clazz) throws JsonProcessingException {
        return MAPPER.readValue(json, clazz);
    }
}

⚠️ 注意事项:

  • ObjectMapper创建成本高,应避免频繁创建
  • 线程安全,可在多线程环境共享
  • 配置后不应再修改,保证线程安全

# 3. JSON与Java对象的相互转换

Jackson提供了将JSON数据与Java对象相互转换的功能。

# 示例类定义

为了演示Jackson的使用,我们定义以下测试类:

// 使用Java 14+的record(推荐)
record MyClass(String name, Integer age) {}

// 或使用传统的POJO类
public class MyClass {
    private String name;
    private Integer age;
    
    // 必须有无参构造函数(用于反序列化)
    public MyClass() {}
    
    public MyClass(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    // Getters and Setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}
  1. JSON字符串转Java对象

要将JSON字符串转换为Java对象,可以使用ObjectMapper的readValue()方法。以下是一个示例:

String jsonString = "{\"name\":\"John\",\"age\":30}";

// JSON字符串转Java对象
MyClass myObject = objectMapper.readValue(jsonString, MyClass.class);

在上面的示例中,我们将JSON字符串转换为MyClass对象。

  1. JSON文件转Java对象

要将JSON文件转换为Java对象,可以使用ObjectMapper的readValue()方法,并将文件作为输入源。以下是一个示例:

File jsonFile = new File("data.json");

// JSON文件转Java对象
MyClass myObject = objectMapper.readValue(jsonFile, MyClass.class);

在上面的示例中,我们将JSON文件转换为MyClass对象。

  1. Java对象转JSON字符串

要将Java对象转换为JSON字符串,可以使用ObjectMapper的writeValueAsString()方法。以下是一个示例:

MyClass myObject = new MyClass("John", 30);

// Java对象转JSON字符串
String jsonString = objectMapper.writeValueAsString(myObject);

在上面的示例中,我们将MyClass对象转换为JSON字符串。

  1. Java对象输出到JSON文件

要将Java对象输出到JSON文件,可以使用ObjectMapper的writeValue()方法,并将文件作为输出目标。以下是一个示例:

MyClass myObject = new MyClass("John", 30);
File jsonFile = new File("output.json");

// Java对象输出到JSON文件
objectMapper.writeValue(jsonFile, myObject);

在上面的示例中,我们将MyClass对象输出到名为"output.json"的文件中。

# 4. JSON与Java集合的相互转换

除了转换单个Java对象,Jackson还提供了将JSON与Java集合相互转换的功能。

  1. JSON字符串转Java集合

要将JSON字符串转换为Java集合,可以使用ObjectMapper的readValue()方法,并指定目标集合类型。以下是一个示例:

String jsonArray = "[{\"name\":\"John\",\"age\":30},{\"name\":\"Alice\",\"age\":25}]";

// JSON字符串转Java集合
MyClass[] myObjects = objectMapper.readValue(jsonArray, MyClass[].class);
// 或者
List<MyClass> myObjects = objectMapper.readValue(jsonArray, new TypeReference<List<MyClass>>() {});

在上面的示例中,我们将JSON数组转换为List<MyClass>集合。

上面代码中我们看到了TypeReference,它是Jackson库中的一个类,用于解决Java泛型类型在序列化和反序列化过程中的类型擦除问题。由于Java的泛型信息在运行时被擦除,无法直接获得泛型类型,因此Jackson提供了TypeReference类来捕获泛型类型的信息。

在使用Jackson进行反序列化时,如果目标类型是一个泛型类型,可以使用TypeReference来指定泛型类型,并获取准确的类型信息,以便正确地进行反序列化。

通过使用TypeReference,我们能够解决Java泛型类型的类型擦除问题,确保在反序列化时获得准确的类型信息,从而正确地转换为目标类型。

你也可以把JSON对象转换为Map对象,例如:

List<Map<String,Object> myObjects = objectMapper.readValue(jsonArray, new TypeReference<List<Map<String,Object>>() {});
  1. JSON文件转Java集合

要将JSON文件转换为Java集合,可以使用ObjectMapper的readValue()方法,并将文件作为输入源。以下是一个示例:

File jsonFile = new File("data.json");

// JSON文件转Java集合
List<MyClass> myObjects = objectMapper.readValue(jsonFile, new TypeReference<List<MyClass>>() {});

在上面的示例中,我们将JSON文件转换为List<MyClass>集合。

  1. Java集合转JSON字符串

要将Java集合转换为JSON字符串,可以使用ObjectMapper的writeValueAsString()方法。 以下是一个示例:

List<MyClass> myObjects = new ArrayList<>();
myObjects.add(new MyClass("John", 30));
myObjects.add(new MyClass("Alice", 25));

// Java集合转JSON字符串
String jsonArray = objectMapper.writeValueAsString(myObjects);

在上面的示例中,我们将List<MyClass>集合转换为JSON字符串。

# 5. 复杂对象的处理

演示一下复杂JSON数据的处理,包括嵌套对象、数组等。

假设有如下类:

public class CompositeClass {
    private String name;
    private int age;
    private Address address;
    private List<Pet> pets;

    // 构造函数、Getter和Setter方法等

    public static class Address {
        private String street;
        private String city;

        // 构造函数、Getter和Setter方法等

        // 省略其他代码
    }

    public static class Pet {
        private String name;
        private String species;

        // 构造函数、Getter和Setter方法等

        // 省略其他代码
    }

    // 省略其他代码
}

要把JSON转换为CompositeClass对象,可以使用ObjectMapper的readValue()方法,并指定目标类型。以下是一个示例:

    String jsonString = "{\"name\":\"John\",\"age\":30,\"address\":{\"street\":\"123 Main St\",\"city\":\"New York\"},\"pets\":[{\"name\":\"Fluffy\",\"species\":\"cat\"},{\"name\":\"Buddy\",\"species\":\"dog\"}]}";

    // 将JSON转换为Java对象
    MyClass myObject = objectMapper.readValue(jsonString, MyClass.class);

    // 访问Java对象中的数据
    String name = myObject.getName();
    int age = myObject.getAge();
    String street = myObject.getAddress().getStreet();
    String city = myObject.getAddress().getCity();
    List<Pet> pets = myObject.getPets();

    System.out.println("Name: " + name);
    System.out.println("Age: " + age);
    System.out.println("Street: " + street);
    System.out.println("City: " + city);
    System.out.println("Pets:");

    for (Pet pet : pets) {
        String petName = pet.getName();
        String species = pet.getSpecies();
        System.out.println("  Pet Name: " + petName);
        System.out.println("  Species: " + species);
    }

# 6. JsonNode的用法

ObjectMapper的readTree()方法接受一个JSON字符串作为参数,并返回一个JsonNode对象,表示解析后的JSON数据的树形结构。

以下是一个示例:

    String jsonString = "{\"name\":\"John\",\"age\":30,\"address\":{\"street\":\"123 Main St\",\"city\":\"New York\"},\"pets\":[{\"name\":\"Fluffy\",\"species\":\"cat\"},{\"name\":\"Buddy\",\"species\":\"dog\"}]}";
    // 将JSON字符串转换为JsonNode对象
    JsonNode jsonNode = objectMapper.readTree(jsonString);

    // 访问JsonNode中的数据
    String name = jsonNode.get("name").asText();
    int age = jsonNode.get("age").asInt();
    String street = jsonNode.get("address").get("street").asText();
    String city = jsonNode.get("address").get("city").asText();
    JsonNode pets = jsonNode.get("pets");

    System.out.println("Name: " + name);
    System.out.println("Age: " + age);
    System.out.println("Street: " + street);
    System.out.println("City: " + city);
    System.out.println("Pets:");

    for (JsonNode pet : pets) {
        String petName = pet.get("name").asText();
        String species = pet.get("species").asText();
        System.out.println("  Pet Name: " + petName);
        System.out.println("  Species: " + species);
    }

# 7. 美化JSON输出

如果需要对生成的JSON进行格式化和美化,可以使用ObjectMapper的writerWithDefaultPrettyPrinter()方法。以下是一个示例:

MyClass myObject = new MyClass("John", 30);

// Java对象转JSON字符串(格式化输出)
String jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(myObject);

# 8. JSON处理的行为和特性配置

ObjectMapper.configure()是Jackson库中ObjectMapper类的一个方法,用于配置ObjectMapper的各种行为和特性。通过该方法,可以自定义和控制ObjectMapper在序列化和反序列化过程中的行为。

configure()方法接受两个参数:一个枚举类型的配置选项,和一个布尔值参数,用于指定配置选项的设置值。下面是一些常用的配置选项及其说明:

  1. DeserializationFeature:用于配置反序列化时的行为选项,例如处理未知属性、忽略空值、支持使用单引号等。

    • FAIL_ON_UNKNOWN_PROPERTIES: 控制是否在遇到未知属性时抛出异常。设置为false表示忽略未知属性,默认为true。
    • ACCEPT_EMPTY_STRING_AS_NULL_OBJECT: 指定是否将空字符串解析为null对象。默认为false。
    • UNWRAP_SINGLE_VALUE_ARRAYS: 控制是否自动将包含单个值的数组解包。默认为false。
    • USE_BIG_DECIMAL_FOR_FLOATS: 控制是否使用BigDecimal来表示浮点数。默认为false,使用double表示浮点数。
    • USE_BIG_INTEGER_FOR_INTS: 控制是否使用BigInteger来表示整数。默认为false,使用int或long表示整数。
  2. SerializationFeature:用于配置序列化时的行为选项,例如处理空值、缩进输出、使用字面值输出枚举等。

    • INDENT_OUTPUT: 控制是否缩进输出的JSON。默认为false。
    • WRITE_DATES_AS_TIMESTAMPS: 控制是否将日期写入为时间戳。默认为true。
    • WRITE_NULL_MAP_VALUES: 控制是否写入空值字段。默认为true。
    • WRITE_ENUMS_USING_TO_STRING: 控制是否使用toString()方法来序列化枚举值。默认为false,使用枚举常量名称。
    • WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS: 控制是否将字符数组写入为JSON数组。默认为false。
  3. JsonParser.Feature:用于配置JSON解析器的行为选项,例如支持单引号、允许注释等。

    • ALLOW_COMMENTS: 控制是否允许在JSON中包含注释。默认为false。
    • ALLOW_SINGLE_QUOTES: 控制是否允许使用单引号作为字符串的引号。默认为false。
    • ALLOW_UNQUOTED_FIELD_NAMES: 控制是否允许在JSON中使用非引号括起的字段名称。默认为false。
  4. JsonGenerator.Feature:用于配置JSON生成器的行为选项,例如缩进输出、使用双引号包装属性等。

    • QUOTE_FIELD_NAMES: 控制是否在生成的JSON中将字段名称用引号括起。默认为true。
    • WRITE_BIGDECIMAL_AS_PLAIN: 控制是否将BigDecimal写入为普通数字,而不是科学计数法。默认为false。

通过调用ObjectMapper.configure()方法,可以根据具体需求设置上述选项及其他选项。以下是一个示例代码,展示了如何使用configure()方法设置ObjectMapper的配置选项:

    ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    // 反序列化时,当遇到未知的属性(在JSON中存在但在目标Java类中不存在)时,控制是否抛出异常。通过将其设置为false,可以忽略未知的属性而不抛出异常。
    OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    // 序列化时,控制是否将字符数组写入为JSON数组。默认情况下,字符数组被序列化为字符串,但将其设置为true可以将字符数组序列化为JSON数组。
    OBJECT_MAPPER.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS, true);
    // 解析时,控制是否允许在JSON中使用单引号作为字符串的引号。将其设置为true可以允许使用单引号来表示字符串,而不仅限于双引号。
    OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

# 9. 时间处理

Jackson默认将Date序列化为时间戳格式(从1970年1月1日UTC开始的毫秒数)。

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    Date date = df.parse("01-01-1970 01:00");
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.writeValueAsString(event);

实际输出的结果如下:

{
    "name": "party",
    "eventDate": 3600000
}

# 配置ObjectMapper日期格式

配置ObjectMapper以允许我们设置日期的格式:

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");

    String toParse = "20-12-2014 02:30";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(df);

    String result = mapper.writeValueAsString(event);
    // result = toParse

请注意,我们使用了全局配置,影响整个ObjectMapper的行为。

# 使用@JsonFormat格式化日期

可以使用@JsonFormat注解在单个类上控制日期格式,而不是全局配置整个应用程序的日期格式:

public class Event {
    public String name;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

现在让我们进行测试:

@Test
public void whenUsingJsonFormatAnnotationToFormatDate_thenCorrect()
throws JsonProcessingException, ParseException {

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

# 自定义日期序列化器

为了完全控制输出结果,我们可以使用自定义的日期序列化器:

public class CustomDateSerializer extends StdSerializer<Date> {

    private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateSerializer() {
        this(null);
    }

    public CustomDateSerializer(Class<Date> t) {
        super(t);
    }
    
    @Override
    public void serialize(Date value, JsonGenerator gen, SerializerProvider provider)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.format(value));
    }
}

现在,我们将它作为"eventDate"字段的序列化器:

public class Event {
    public String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
}

最后,让我们进行测试:

@Test
public void whenUsingCustomDateSerializer_thenCorrect()
throws JsonProcessingException, ParseException {

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

# 使用Jackson序列化Joda-Time

日期并不总是java.util.Date的实例。实际上,越来越多的情况下,日期是由其他类表示的,而常见的一种是Joda-Time库中的DateTime类。

让我们看看如何使用Jackson对DateTime进行序列化。

我们将使用jackson-datatype-joda模块来支持Joda-Time的序列化:

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-joda</artifactId>
  <version>2.15.2</version>
</dependency>

然后,我们只需要注册JodaModule即可:

@Test
public void whenSerializingJodaTime_thenCorrect()
throws JsonProcessingException {
    DateTime date = new DateTime(2014, 12, 20, 2, 30,
        DateTimeZone.forID("Europe/London"));

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JodaModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30:00.000Z"));
}

# 使用Jackson序列化Java 8日期

现在让我们看看如何使用Jackson对Java 8日期(例如LocalDateTime)进行序列化。我们可以使用jackson-datatype-jsr310模块:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.15.2</version>
</dependency>

然后,我们只需要注册JavaTimeModule(JSR310Module已被弃用),Jackson将会处理剩下的事情:

@Test
public void whenSerializingJava8Date_thenCorrect()
throws JsonProcessingException {
    LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30);

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30"));
}

# 10. 自定义序列化和反序列化

在时间处理一章中,我们已经做到了自定义序列化处理,现在补充一下自定义Date的反序列化处理:

public class CustomDateDeserializer extends StdDeserializer<Date> {

    private SimpleDateFormat formatter = 
      new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateDeserializer() {
        this(null);
    }

    public CustomDateDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Date deserialize(JsonParser jsonparser, DeserializationContext context)
      throws IOException, JsonProcessingException {
        String date = jsonparser.getText();
        try {
            return formatter.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

接下来,我们将将其用作"eventDate"的反序列化器:

public class Event {
    public String name;

    @JsonDeserialize(using = CustomDateDeserializer.class)
    public Date eventDate;
}

最后,我们将进行测试:

@Test
public void whenDeserializingDateUsingCustomDeserializer_thenCorrect()
throws JsonProcessingException, IOException {

    String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014 02:30:00\"}";

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    ObjectMapper mapper = new ObjectMapper();

    Event event = mapper.readerFor(Event.class).readValue(json);
    assertEquals("20-12-2014 02:30:00", df.format(event.eventDate));
}

# Jackson的datatype模块

在前面我们已经看到了Jackson本身提供了很多datatype模块,用于提供对不同数据类型的序列化和反序列化支持。它包含了一些特定类型的序列化和反序列化器,使得在处理特定数据类型时更加方便和灵活。

除了日期和时间类型,datatype模块还提供了对其他数据类型的支持,如Guava类型、Java8类型、PCollections类型等。通过使用相应的模块,可以轻松地处理这些特定类型的序列化和反序列化。

要在项目中使用datatype模块,需要将相应的依赖项添加到项目的构建文件(如pom.xml)。具体依赖项的名称和版本号可以根据需要和使用的Jackson版本进行调整。

以下是常用的datatype模块依赖项:

  • jackson-datatype-jsr310:提供对Java 8日期和时间API的支持。
  • jackson-module-parameter-names:提供了对使用新的JDK 8特性的支持的模块,能够访问构造函数和方法参数的名称,以允许省略@JsonProperty。
  • jackson-datatype-jdk8:支持除日期/时间类型以外的JDK 8数据类型,包括Optional。
  • jackson-datatype-joda:提供对Joda-Time库的支持。
  • jackson-datatype-guava:提供对Guava库的支持。
  • jackson-datatype-pcollections:提供对PCollections库的支持。

在将这些依赖项添加到项目中后,可以通过注册相应的模块来启用它们,例如使用ObjectMapper的registerModule方法。

更多datatype,参考 Third-party datatype modules (opens new window)

# 注册自己的Module

在使用Guava的Table类型时,发现并没有对于Table类反序列化的支持。可以自己来添加支持:

    ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    // 添加序列化方案
    OBJECT_MAPPER.registerModule(new GuavaModule());
    // Guava的Table没有反序列化方案,使用自定义方案
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addDeserializer(Table.class, new TableDeserializer());
    OBJECT_MAPPER.registerModule(simpleModule);
        
    /**
     * Guavua Table的反序列化方案
     */
    public static class TableDeserializer extends JsonDeserializer<Table<?, ?, ?>> {

        @Override
        public Table<?, ?, ?> deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
            final ImmutableTable.Builder<Object, Object, Object> tableBuilder = ImmutableTable.builder();
            @SuppressWarnings("unchecked") final Map<Object, Map<Object, Object>> rowMap = jp.readValueAs(Map.class);
            for (final Map.Entry<Object, Map<Object, Object>> rowEntry : rowMap.entrySet()) {
                final Object rowKey = rowEntry.getKey();
                for (final Map.Entry<Object, Object> cellEntry : rowEntry.getValue().entrySet()) {
                    final Object colKey = cellEntry.getKey();
                    final Object val = cellEntry.getValue();
                    tableBuilder.put(rowKey, colKey, val);
                }
            }
            return tableBuilder.build();
        }
    }

# 11. Jackson的注解

在时间处理一节,我们已经领略了@JsonFormat注解的使用。

Jackson提供了一系列的注解,用于控制序列化和反序列化过程中的行为。

下面对一些常用的注解进行说明:

  1. @JsonIgnore:

    • 作用: 标记某个字段或方法,在序列化和反序列化过程中忽略该属性。
    • 示例代码:
    public class Person {
        private String name;
        @JsonIgnore
        private int age;
    
        // 省略构造函数和其他方法
    
        // getters and setters
    }
    
  2. @JsonProperty:

    • 作用: 指定字段或方法在序列化和反序列化中的名称。
    • 示例代码:
    public class Person {
        @JsonProperty("full_name")
        private String name;
    
        // 省略构造函数和其他方法
    
        // getters and setters
    }
    
  3. @JsonAnyGetter:

    • 作用: 在序列化时将Java对象的动态属性作为键值对添加到JSON中。
    • 示例代码:
    public class Person {
        private Map<String, Object> properties = new HashMap<>();
    
        public void setProperty(String key, Object value) {
            properties.put(key, value);
        }
    
        @JsonAnyGetter
        public Map<String, Object> getProperties() {
            return properties;
        }
    }
    
  4. @JsonPropertyOrder:

    • 作用: 指定序列化时属性的顺序。
    • 示例代码:
    @JsonPropertyOrder({ "name", "age", "email" })
    public class Person {
        private String name;
        private int age;
        private String email;
    
        // 省略构造函数和其他方法
    
        // getters and setters
    }
    
  5. @JsonRawValue:

    • 作用: 将字符串属性的原始值直接输出,而不进行额外的转义或引号添加。
    • 示例代码:
    public class Person {
        private String description;
    
        @JsonRawValue
        public String getDescription() {
            return description;
        }
    
        // 省略构造函数和其他方法
    
        // setters
    }
    
  6. @JsonAlias:

    • 作用: 定义一个或多个替代名称,用于在反序列化过程中匹配JSON属性。
    • 示例代码:
    public class Person {
        @JsonAlias({ "firstName", "givenName" })
        private String name;
    
        // 省略构造函数和其他方法
    
        // getters and setters
    }
    
  7. @JsonIgnoreType:

    • 作用: 在序列化和反序列化过程中忽略指定的类型。
    • 示例代码:
    @JsonIgnoreType
    public class SecretDetails {
        private String password;
        private String secretKey;
    
        // 省略构造函数和其他方法
    
        // getters and setters
    }
    
    public class Person {
        private String name;
        private int age;
        private SecretDetails secretDetails;
    
        // 省略构造函数和其他方法
    
        // getters and setters
    }
    

Jackson还提供了很多其他注解,请参考 Jackson Annotations (opens new window) 和 Jackson Annotation Examples (opens new window)

# 12. 对其他数据格式的支持

除了默认的JSON格式外,Jackson提供了很多扩展模块。这些扩展模块提供了对各种数据格式的支持,如XML、YAML、CSV等。

Jackson的Data format modules提供了以下功能:

  1. XML支持(Jackson XML Module): Jackson XML Module允许你将Java对象序列化为XML格式,或将XML反序列化为Java对象。它提供了与Jackson JSON库相似的API和注解,用于控制XML序列化和反序列化的行为。

  2. YAML支持(Jackson YAML Module): Jackson YAML Module允许你将Java对象序列化为YAML格式,或将YAML反序列化为Java对象。它提供了简单而直观的API和注解,用于处理YAML数据。

  3. CSV支持(Jackson CSV Module): Jackson CSV Module提供了对CSV(逗号分隔值)格式的支持。它允许你将Java对象序列化为CSV格式,或将CSV反序列化为Java对象。你可以通过注解或配置来控制CSV的映射规则。

  4. Protobuf支持(Jackson Protobuf Module): Jackson Protobuf Module提供了对Google Protobuf格式的支持。它允许你将Protobuf消息序列化为JSON或XML,或将JSON或XML反序列化为Protobuf消息。

  5. CBOR支持(Jackson CBOR Module): Jackson CBOR Module提供了对CBOR(Concise Binary Object Representation)格式的支持。它允许你将Java对象序列化为CBOR格式,或将CBOR反序列化为Java对象。CBOR是一种紧凑的二进制格式,适用于高效的数据传输和存储。

除了上述提到的模块,Jackson还提供了其他数据格式的支持扩展模块,如Smile(二进制JSON格式)和Properties文件格式。

使用这些Data format modules,你可以在Jackson中处理不同的数据格式,实现灵活的数据转换和交互。你可以根据需要选择相应的模块,并根据模块的文档和示例进行配置和使用。这些模块的引入通常需要添加相应的依赖项到项目的构建文件中。

以上的支持,你都可以在 Data format modules (opens new window) 中找到。

# 13. 其他

Jackson还提供了以下功能:

  • 对于JAX-RS的支持 (opens new window)
  • JSON Views (opens new window)

# 性能优化建议

# 1. ObjectMapper复用

// ❌ 错误:每次创建新实例
public String toJson(Object obj) {
    return new ObjectMapper().writeValueAsString(obj);
}

// ✅ 正确:复用单例
private static final ObjectMapper MAPPER = new ObjectMapper();
public String toJson(Object obj) {
    return MAPPER.writeValueAsString(obj);
}

# 2. 使用@JsonProperty减少反射

public class Person {
    @JsonProperty("n")
    private String name;
    
    @JsonProperty("a")
    private int age;
}

# 3. 避免使用JsonNode进行大量数据处理

  • JsonNode适合动态JSON,但性能不如数据绑定
  • 大数据量时优先使用流式API(JsonParser/JsonGenerator)

# 4. 合理配置Feature

// 关闭不需要的特性提升性能
mapper.configure(SerializationFeature.INDENT_OUTPUT, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

# 常见问题与解决方案

# 1. 循环引用

@JsonManagedReference  // 父端
private List<Child> children;

@JsonBackReference     // 子端
private Parent parent;

# 2. 日期格式问题

// 全局配置
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

// 或使用Java 8时间API
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

# 3. 大文件处理

// 使用流式API处理大文件
try (JsonParser parser = mapper.getFactory().createParser(new File("large.json"))) {
    while (parser.nextToken() != JsonToken.END_OBJECT) {
        // 逐个处理token
    }
}

# 总结

本文全面介绍了Java中JSON处理的方方面面,重点讲解了Jackson库的使用:

  1. 基础知识:JSON格式、数据类型、应用场景
  2. 库对比:Jackson、Gson、Fastjson2的特点和选择
  3. 核心用法:对象映射、集合处理、复杂类型
  4. 高级特性:自定义序列化、注解体系、多格式支持
  5. 最佳实践:性能优化、常见问题解决

Jackson作为Java生态中最成熟的JSON处理库,提供了:

  • 🚀 卓越的性能
  • 🎯 完整的功能特性
  • 🔧 灵活的配置选项
  • 📚 丰富的扩展生态

掌握Jackson的使用,将极大提升你在Java项目中处理JSON数据的效率和质量。

祝你变得更强!

编辑 (opens new window)
#Jackson使用教程#JSON
上次更新: 2025/08/15
Java日志系统
Java XML处理

← Java日志系统 Java XML处理→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式