JSON介绍
JSON 是一种轻量级的数据交换格式,基于 JavaScript 编程语言标准 ECMA-262 第 3 版(1999年12月),它既便于人类阅读和编写,也方便机器解析和生成。虽然 JSON 是一种独立于语言的文本格式,但它使用了许多语言(如 C、C++、C#、Java、JavaScript、Perl、Python 等)程序员熟悉的格式。因此,JSON 成为一种理想的数据交换格式,适用于跨语言和平台的数据传输。
JSON 的两种基本结构
- 名称/值对集合:这种结构通常在不同编程语言中实现为对象、记录、结构体、字典、哈希表、键控列表或关联数组。它是 JSON 数据的核心,表示一个由键(名称)和值组成的集合。
- 有序值的列表:在多数编程语言中,这种结构通常表现为数组、向量、列表或序列。它用于表示一组有序的数据项。
合法的 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 字段。
- 定义视图类
public class Views {
public static class Public {}
public static class Internal extends Public {}
}
这里我们创建了两个视图:Public 和 Internal,Internal 继承自 Public,表示 Internal 可以看到 Public 中的字段,同时还可以查看更多的字段。
- 在实体类上使用 @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 视图。
- 在控制器中指定视图
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,你可以为某个字段或类型指定一个自定义的序列化器。
- 为字段或方法指定自定义序列化器:使用 @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 默认的序列化方式。
- 创建自定义序列化器:自定义的序列化器需要继承 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 格式的字符串。
- 为类指定自定义序列化器:除了为字段指定自定义序列化器外,你还可以为整个类指定自定义序列化器。
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 来进行序列化。
- 创建自定义类序列化器:和字段序列化器类似,你也需要创建一个自定义的类序列化器。自定义类序列化器需要继承 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 在反序列化时,应该调用哪个构造函数或静态工厂方法来创建对象。它通常与构造函数或静态方法一起使用,特别是在需要自定义反序列化过程时。
- 与构造函数一起使用:使用 @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;
}
...
}
- 与静态工厂方法一起使用:使用 @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 对象。
-
日期格式:默认情况下,Spring Boot 使用 Jackson 的 ISO 8601 格式来处理日期和时间。这意味着日期默认会使用格式 yyyy-MM-dd’T’HH:mm:ss.SSSZ,也就是类似 2025-07-14T15:08:00.000+0000 这样的格式。
-
忽略未知属性:默认情况下,Spring Boot 配置 Jackson 以 忽略未知属性。即如果 JSON 数据中包含在 Java 对象中没有的字段,Jackson 会忽略这些字段,而不会抛出异常。
-
空值处理:默认情况下,Spring Boot 配置 Jackson 包含 null 值的属性,即序列化时 null 字段会出现在输出的 JSON 中。如果需要忽略 null 字段,可以通过自定义 ObjectMapper 来修改这个行为。
-
枚举处理:Spring Boot 默认的 Jackson 配置会使用 枚举的名称(即 name() 方法)进行序列化。如果你希望枚举值使用其他格式,可以通过自定义 ObjectMapper 或使用 @JsonValue 注解来调整。
-
字符编码:默认情况下,Jackson 会使用 UTF-8 编码进行 JSON 数据的读写。
-
缩进格式:默认情况下,JSON 输出不会进行缩进。如果想要格式化 JSON 输出,可以使用 @JsonPropertyOrder 或在配置文件中修改。
一个Spring返回JSON字符串的细节
如果在 Spring 中返回了普通字符串(如 abc),并且 Content-Type 设置为 application/json,那么服务端会出错吗?
答案是不会,因为:
-
Spring 处理响应时不检查内容是否合法
当你在控制器方法中返回字符串时,Spring 会将这个字符串直接写入响应体,它会设置响应头(例如 Content-Type: application/json),但是 Spring 并不会主动检查内容是否符合 application/json 格式。 -
服务端不做 JSON 验证
Spring 主要是在处理请求时验证请求体是否符合期望的 JSON 格式(例如,在 @RequestBody 注解中),而对于响应体,它只是根据 @ResponseBody 或 @RestController 的配置将返回的对象序列化为 JSON 格式。如果你返回一个简单的字符串,Spring 就直接将它当作普通的响应内容返回。
参考
https://www.json.org/json-en.html
https://www.baeldung.com/jackson
9050

被折叠的 条评论
为什么被折叠?



