Jackson:从入门到熟手,Java 处理 JSON 的终极指南

JSON介绍

JSON 是一种轻量级的数据交换格式,基于 JavaScript 编程语言标准 ECMA-262 第 3 版(1999年12月),它既便于人类阅读和编写,也方便机器解析和生成。虽然 JSON 是一种独立于语言的文本格式,但它使用了许多语言(如 C、C++、C#、Java、JavaScript、Perl、Python 等)程序员熟悉的格式。因此,JSON 成为一种理想的数据交换格式,适用于跨语言和平台的数据传输。

JSON 的两种基本结构

  1. 名称/值对集合:这种结构通常在不同编程语言中实现为对象、记录、结构体、字典、哈希表、键控列表或关联数组。它是 JSON 数据的核心,表示一个由键(名称)和值组成的集合。
  2. 有序值的列表:在多数编程语言中,这种结构通常表现为数组、向量、列表或序列。它用于表示一组有序的数据项。

合法的 JSON 必须以对象或数组开头,即必须以 { 或 [ 开头,不能是其他数据类型(如字符串、数字等)直接作为开头。


Jackson介绍

使用 Jackson 需要引入以下依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.2</version>
</dependency>

此依赖项还会以传递方式将以下库添加到 Classpath 中:

  • jackson-annotations(Jackson 注解)
  • jackson-core(Jackson 核心)

建议始终使用 Maven 中央仓库中最新版本的 jackson-databind。


核心工具类 ObjectMapper

Java对象转JSON

让我们看一下使用 ObjectMapper 类的 writeValue 或 writeValueAsString 方法将 Java 对象序列化为 JSON 的第一个示例:

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
// 写入文件
objectMapper.writeValue(new File("target/car.json"), car);
// 写入字符串
String carAsString = objectMapper.writeValueAsString(car);

JSON转Java对象

使用 ObjectMapper 类将 JSON 字符串转换为 Java 对象的简单示例:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Car car = objectMapper.readValue(json, Car.class);	

readValue 函数还接受其他形式的输入,例如包含 JSON 字符串的文件或 URL。

JSON转JsonNode

可以将 JSON 解析为 JsonNode 对象,并用于从特定节点检索数据:

String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
String color = jsonNode.get("color").asText();
// 输出:Black

JSON数组转为Java列表

可以使用 TypeReference 将数组形式的 JSON 解析为 Java 对象列表:

String jsonCarArray = 
  "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});

JSON对象转为Java哈希表

同样,我们可以将 JSON 解析为 Java Map

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Map<String, Object> map 
  = objectMapper.readValue(json, new TypeReference<Map<String,Object>>(){});

反序列化之多余字段

例如,假设我们需要将以下 JSON 字符串反序列化为 Java 对象,Jackson 将抛出异常,因为 JSON 字符串有一个多余字段 ext

public class MyDto {
    private String stringValue;
    private int intValue;
    private boolean booleanValue;

    // standard constructor, getters and setters 
}

public void givenJsonHasUnknownValues_whenDeserializingAJsonToAClass_thenExceptionIsThrown()
  throws JsonParseException, JsonMappingException, IOException {
    String jsonAsString =
        "{\"stringValue\":\"a\"," +
        "\"intValue\":1," +
        "\"booleanValue\":true," +
        "\"ext\":\"something\"}";
    ObjectMapper mapper = new ObjectMapper();

    MyDto readValue = mapper.readValue(jsonAsString, MyDto.class);

    assertNotNull(readValue);
}

配置 ObjectMapper 级别忽略多余字段

new ObjectMapper()
  .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

配置 Class 级别忽略多余字段

@JsonIgnoreProperties(ignoreUnknown = true)
public class MyDtoIgnoreUnknown { ... }

序列化之忽略字段

使用 @JsonIgnoreProperties 在类级别忽略字段

@JsonIgnoreProperties(value = { "intValue" })
public class MyDto {
    private String stringValue;
    private int intValue;
    private boolean booleanValue;

    // standard setters and getters are not shown
}

使用 @JsonIgnore 在字段级别忽略字段

public class MyDto {
    private String stringValue;
    @JsonIgnore
    private int intValue;
    private boolean booleanValue;

    // standard setters and getters are not shown
}

使用 @JsonInclude(Include.NON_NULL) 忽略 Null 字段

在 class 级别忽略 null 字段

@JsonInclude(Include.NON_NULL)
public class MyDto { ... }

在字段级别忽略 null 字段

public class MyDto {
    @JsonInclude(Include.NON_NULL)
    private String stringValue;

    private int intValue;

    // standard getters and setters
}

全局忽略 Null 字段

mapper.setSerializationInclusion(Include.NON_NULL);

日期处理

默认日期格式——时间戳

public class User {
    private String firstName;
    private String lastName;
    private Date createdDate = new Date();

    // standard constructor, setters and getters
}
// 此代码示例将返回以下输出:
{"firstName":"John","lastName":"Smith","createdDate":1482047026009}

默认序列化为这种简洁的时间戳格式不方便人类阅读,因此,有时候我们需要将 Date 序列化为 ISO-8601 格式。

使用 ObjectMapper 格式化日期

配置 ObjectMapper 控制全局的日期格式

@Test
public void whenSettingObjectMapperDateFormat_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    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);
    assertThat(result, containsString(toParse));
}

使用 @JsonFormat 格式化日期

使用 @JsonFormat 注解控制字段级别的日期格式,而不是全局的日期格式

public class Event {
    public String name;

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

自定义序列化器

继承 StdSerializer 类

public class ItemSerializer extends StdSerializer<Item> {
    public ItemSerializer() {
        this(null);
    }
  
    public ItemSerializer(Class<Item> t) {
        super(t);
    }

    @Override
    public void serialize(
      Item value, JsonGenerator jgen, SerializerProvider provider) 
      throws IOException, JsonProcessingException {
 
        jgen.writeStartObject();
        jgen.writeNumberField("id", value.id);
        jgen.writeStringField("itemName", value.itemName);
        jgen.writeNumberField("owner", value.owner.id);
        jgen.writeEndObject();
    }
}

然后向 ObjectMapper 注册这个自定义序列化器,并执行序列化:

Item myItem = new Item(1, "theItem", new User(2, "theUser"));
ObjectMapper mapper = new ObjectMapper();

SimpleModule module = new SimpleModule();
module.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(module);

String serialized = mapper.writeValueAsString(myItem);

或者使用 @JsonSerialize 来注册自定义序列化器

@JsonSerialize(using = ItemSerializer.class)
public class Item {
    ...
}

@JsonValue

@JsonValue 可以标记类中的一个方法,目的是让 Jackson 在序列化该对象时,将方法的返回值或字段的值作为 JSON 中的直接值,而不是将整个对象转换为一个复杂的 JSON 结构。

import com.fasterxml.jackson.annotation.JsonValue;

public class Color {

    private String name;

    public Color(String name) {
        this.name = name;
    }

    @JsonValue  // 这个注解告诉 Jackson 使用这个方法返回的值作为 JSON 中的值
    public String getName() {
        return name;
    }

    // 其他方法可以省略
}

将 Color 对象序列化为 JSON 时,Jackson 会使用 getName() 方法返回的字符串作为 JSON 的值,而不是序列化整个 Color 对象:

Color color = new Color("Red");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(color);
System.out.println(json);

@JsonView

@JsonView 是 Jackson 提供的一个注解,用于在 JSON 序列化过程中,按照不同的视图(View)来控制哪些字段被序列化或反序列化。这个功能常常用于场景中,要求根据不同的用户权限或请求场景返回不同的数据字段。

假设你有一个 User 类,包含一些字段(例如,name, email, password),对于某些用户角色,可能希望在返回数据时隐藏 password 字段。

  1. 定义视图类
public class Views {
    public static class Public {}
    public static class Internal extends Public {}
}

这里我们创建了两个视图:Public 和 Internal,Internal 继承自 Public,表示 Internal 可以看到 Public 中的字段,同时还可以查看更多的字段。

  1. 在实体类上使用 @JsonView
import com.fasterxml.jackson.annotation.JsonView;

public class User {

    @JsonView(Views.Public.class)
    private String name;

    @JsonView(Views.Public.class)
    private String email;

    @JsonView(Views.Internal.class)
    private String password;

    // Getters and Setters
}

在这个例子中,name 和 email 字段属于 Public 视图,而 password 字段仅属于 Internal 视图。

  1. 在控制器中指定视图
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(Views.Public.class) // 返回 Public 视图
    public User getPublicUser() {
        return new User("John Doe", "john@example.com", "secretpassword");
    }

    @GetMapping("/user/internal")
    @JsonView(Views.Internal.class) // 返回 Internal 视图
    public User getInternalUser() {
        return new User("John Doe", "john@example.com", "secretpassword");
    }
}

当你请求 /user 时,响应会包含 name 和 email 字段,但不会包含 password。
当你请求 /user/internal 时,响应会包含 name, email 和 password 字段。

@JsonView 只会影响序列化(将对象转换为 JSON),如果需要控制反序列化行为,通常需要额外的配置。

@JsonSerialize

使用 @JsonSerialize,你可以为某个字段或类型指定一个自定义的序列化器。

  1. 为字段或方法指定自定义序列化器:使用 @JsonSerialize 注解将一个字段与自定义的 JsonSerializer 关联起来:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public class User {
    private String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    private Date birthDate;

    // Getters and Setters
}

在这个例子中,birthDate 字段将使用 CustomDateSerializer 来序列化,而 name 字段仍然使用 Jackson 默认的序列化方式。

  1. 创建自定义序列化器:自定义的序列化器需要继承 JsonSerializer 类,并重写 serialize 方法:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CustomDateSerializer extends JsonSerializer<Date> {

    @Override
    public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 自定义日期格式化
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String formattedDate = sdf.format(value);
        gen.writeString(formattedDate);  // 输出日期字符串
    }
}

在这个自定义序列化器 CustomDateSerializer 中,我们使用 SimpleDateFormat 将 Date 转换为 yyyy-MM-dd 格式的字符串。

  1. 为类指定自定义序列化器:除了为字段指定自定义序列化器外,你还可以为整个类指定自定义序列化器。
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@JsonSerialize(using = CustomUserSerializer.class)
public class User {
    private String name;
    private Date birthDate;

    // Getters and Setters
}

在这个例子中,User 类的所有实例都会使用 CustomUserSerializer 来进行序列化。

  1. 创建自定义类序列化器:和字段序列化器类似,你也需要创建一个自定义的类序列化器。自定义类序列化器需要继承 JsonSerializer,其中 T 是类的类型:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;

public class CustomUserSerializer extends JsonSerializer<User> {

    @Override
    public void serialize(User user, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeStartObject();
        gen.writeStringField("fullName", user.getName());  // 序列化时修改字段名
        gen.writeStringField("birth", user.getBirthDate().toString()); // 自定义字段格式
        gen.writeEndObject();
    }
}

在这个自定义类序列化器中,我们修改了 User 类的字段名,并改变了字段值的格式。


自定义反序列化器

继承 StdDeserializer 类

public class ItemDeserializer extends StdDeserializer<Item> { 
    public ItemDeserializer() { 
        this(null); 
    } 

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

    @Override
    public Item deserialize(JsonParser jp, DeserializationContext ctxt) 
      throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        int id = (Integer) ((IntNode) node.get("id")).numberValue();
        String itemName = node.get("itemName").asText();
        int userId = (Integer) ((IntNode) node.get("createdBy")).numberValue();

        return new Item(id, itemName, new User(userId, null));
    }
}

正如我们所看到的,反序列化器正在使用 JSON 的标准 Jackson 表示形式 — JsonNode,一旦输入 JSON 表示为 JsonNode,我们现在就可以从中提取相关信息并构建我们的实体。

然后向 ObjectMapper 注册这个自定义反序列化器

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new ItemDeserializer());
mapper.registerModule(module);

Item readValue = mapper.readValue(json, Item.class);

或者使用 @JsonDeserialize 来注册自定义序列化器

@JsonDeserialize(using = ItemDeserializer.class)
public class Item {
    ...
}

@JsonCreator

@JsonCreator 是 Jackson 提供的一个注解,用于指示 Jackson 在反序列化时,应该调用哪个构造函数或静态工厂方法来创建对象。它通常与构造函数或静态方法一起使用,特别是在需要自定义反序列化过程时。

  1. 与构造函数一起使用:使用 @JsonCreator 注解在类的构造函数上,告诉 Jackson 使用这个构造函数来反序列化对象。
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {

    private String name;
    private int age;

    @JsonCreator  // 标注构造函数,告诉 Jackson 使用此构造函数
    public Person(@JsonProperty("name") String name, @JsonProperty("age") int age) {
        this.name = name;
        this.age = age;
    }
	...
}
  1. 与静态工厂方法一起使用:使用 @JsonCreator 注解来标记一个静态工厂方法,而不是构造函数,这对于需要特殊处理或更加复杂的对象创建过程时非常有用。
public class Person {
    private String name;
    private int age;

    @JsonCreator
    public static Person createPerson(@JsonProperty("name") String name, @JsonProperty("age") int age) {
        return new Person(name, age);  // 返回一个新创建的 Person 对象
    }
	
	...
}

使用 @JsonCreator 后,Jackson 不会调用 getter 和 setter 方法,而是直接通过构造函数(或静态方法)进行反序列化。如果没有使用 @JsonCreator,Jackson 会使用默认构造函数并通过 setter 方法来填充字段。


自定义字段名称

让我们考虑一个示例 User 类:

public class User {
    private String firstName;
    private String lastName;

    // standard getters and setters
}

让我们尝试加载此 JSON,它使用蛇形大小写命名标准(小写名称以 _ 分隔):

{
    "first_name": "Jackie",
    "last_name": "Chan"
}

首先,我们需要使用 ObjectMapper 来反序列化这个 JSON:

ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(JSON, User.class);

但是,当我们尝试此作时,我们会收到一个错误,Jackson 无法将 JSON 中的名称与 User 中的字段名称完全匹配。

@JsonNaming

在 class 上使用 @JsonNaming 注解, 所有字段都将使用 snake case 进行反序列化

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class UserWithSnakeStrategy {
    private String firstName;
    private String lastName;

    // standard getters and setters
}

直接配置 ObjectMapper

ObjectMapper objectMapper = new ObjectMapper()
  .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

@JsonProperty

在类的字段上使用 @JsonProperty 注释将字段映射到 JSON 中的确切名称

public class UserWithPropertyNames {
    @JsonProperty("first_name")
    private String firstName;
    
    @JsonProperty("last_name")
    private String lastName;

    // standard getters and setters
}

Spring Boot 默认的 Jackson 配置

ObjectMapper 配置:Spring Boot 会自动配置一个默认的 ObjectMapper 实例,它负责将 Java 对象转换为 JSON 以及将 JSON 转换为 Java 对象。

  1. 日期格式:默认情况下,Spring Boot 使用 Jackson 的 ISO 8601 格式来处理日期和时间。这意味着日期默认会使用格式 yyyy-MM-dd’T’HH:mm:ss.SSSZ,也就是类似 2025-07-14T15:08:00.000+0000 这样的格式。

  2. 忽略未知属性:默认情况下,Spring Boot 配置 Jackson 以 忽略未知属性。即如果 JSON 数据中包含在 Java 对象中没有的字段,Jackson 会忽略这些字段,而不会抛出异常。

  3. 空值处理:默认情况下,Spring Boot 配置 Jackson 包含 null 值的属性,即序列化时 null 字段会出现在输出的 JSON 中。如果需要忽略 null 字段,可以通过自定义 ObjectMapper 来修改这个行为。

  4. 枚举处理:Spring Boot 默认的 Jackson 配置会使用 枚举的名称(即 name() 方法)进行序列化。如果你希望枚举值使用其他格式,可以通过自定义 ObjectMapper 或使用 @JsonValue 注解来调整。

  5. 字符编码:默认情况下,Jackson 会使用 UTF-8 编码进行 JSON 数据的读写。

  6. 缩进格式:默认情况下,JSON 输出不会进行缩进。如果想要格式化 JSON 输出,可以使用 @JsonPropertyOrder 或在配置文件中修改。


一个Spring返回JSON字符串的细节

如果在 Spring 中返回了普通字符串(如 abc),并且 Content-Type 设置为 application/json,那么服务端会出错吗?

答案是不会,因为:

  1. Spring 处理响应时不检查内容是否合法
    当你在控制器方法中返回字符串时,Spring 会将这个字符串直接写入响应体,它会设置响应头(例如 Content-Type: application/json),但是 Spring 并不会主动检查内容是否符合 application/json 格式。

  2. 服务端不做 JSON 验证
    Spring 主要是在处理请求时验证请求体是否符合期望的 JSON 格式(例如,在 @RequestBody 注解中),而对于响应体,它只是根据 @ResponseBody 或 @RestController 的配置将返回的对象序列化为 JSON 格式。如果你返回一个简单的字符串,Spring 就直接将它当作普通的响应内容返回。


参考

https://www.json.org/json-en.html
https://www.baeldung.com/jackson

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值