
本教程深入探讨了如何使用jackson库处理复杂的json反序列化场景。我们将学习如何通过`@jsonformat(shape = jsonformat.shape.array)`注解将2d json数组映射到java对象数组,以及如何利用`@jsoncreator`工厂方法和`map
在使用Jackson进行JSON反序列化时,我们通常期望JSON对象的字段能够直接映射到Java对象的属性。然而,当JSON结构变得复杂,例如一个Java对象被表示为JSON数组,或者同一个逻辑实体在不同的JSON输入中具有完全不同的结构时,标准的映射机制可能会遇到挑战。本文将详细介绍如何利用Jackson的强大功能来解决这些问题。
1. 处理2D JSON数组到对象数组的映射
假设我们有一个表示经纬度坐标的Java记录类LngLat,以及一个包含多个LngLat坐标的NoFlyZone记录类。
// LngLat 记录类,表示经纬度
record LngLat(double lng, double lat) {}
// NoFlyZone 记录类,包含一个 LngLat 数组
record NoFlyZone(LngLat[] coordinates) {}我们可能会遇到以下形式的JSON数据,其中coordinates是一个二维数组,每个内层数组[-3.1, 55.4]代表一个LngLat对象:
String json1 = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";如果直接使用ObjectMapper尝试反序列化,Jackson会抛出MismatchedInputException,因为它期望LngLat是一个JSON对象(例如{"lng":-3.1, "lat":55.4}),但实际遇到的是一个JSON数组。
为了解决这个问题,我们需要告诉Jackson,LngLat对象可以通过JSON数组的形式来表示。这可以通过在LngLat记录类上添加@JsonFormat(shape = JsonFormat.Shape.ARRAY)注解来实现。该注解指示Jackson按照记录中字段的声明顺序,将JSON数组的元素映射到对应的属性。
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
// LngLat 记录类,使用 @JsonFormat 注解支持数组形式的 JSON
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
record LngLat(double lng, double lat) {
// 覆盖 toString() 方法便于输出观察
@Override
public String toString() {
return "LngLat[lng=" + lng + ", lat=" + lat + "]";
}
}
// NoFlyZone 记录类,coordinates 属性将自动处理 LngLat 数组
@JsonIgnoreProperties("name") // 忽略 JSON 中的 "name" 字段
record NoFlyZone(LngLat[] coordinates) {
// 覆盖 toString() 方法便于输出观察
@Override
public String toString() {
return "NoFlyZone{coordinates=" + java.util.Arrays.toString(coordinates) + "}";
}
}现在,我们可以成功地反序列化包含2D数组的JSON字符串:
public class ArrayDeserializationExample {
public static void main(String[] args) throws Exception {
String json1 = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
ObjectMapper mapper = new ObjectMapper();
List noFlyZones = mapper.readValue(json1, new TypeReference>() {});
System.out.println("Deserialized JSON 1: " + noFlyZones);
}
}
输出示例:
Deserialized JSON 1: [NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]2. 处理多态JSON结构:动态创建对象
在某些情况下,同一个Java对象可能需要从两种或更多种完全不同的JSON结构中反序列化。例如,除了上述的JSON 1,我们可能还会遇到以下形式的JSON 2,其中经纬度直接作为NoFlyZone的属性:
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]";如果LngLat仍然使用@JsonFormat(shape = JsonFormat.Shape.ARRAY),那么反序列化JSON 2时,Jackson将无法找到LngLat的数组表示,导致新的错误。为了同时支持这两种完全不同的JSON结构,我们需要在更高层次(即NoFlyZone级别)进行自定义反序列化。
我们可以通过在NoFlyZone记录类中引入一个静态工厂方法,并使用@JsonCreator注解来指示Jackson使用此方法创建对象。这个工厂方法可以接收一个Map
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException;
import java.util.List;
import java.util.Map;
// LngLat 记录类保持不变,仍然使用 @JsonFormat 支持数组形式
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
record LngLat(double lng, double lat) {
@Override
public String toString() {
return "LngLat[lng=" + lng + ", lat=" + lat + "]";
}
}
// NoFlyZone 记录类,使用 @JsonCreator 处理多态 JSON 结构
@JsonIgnoreProperties(ignoreUnknown = true) // 忽略 JSON 中未映射的字段,如 "name"
public record NoFlyZone(LngLat[] coordinates) {
@JsonCreator
public static NoFlyZone getInstance(Map fields) throws IOException {
LngLat[] longLatArray;
// 判断 JSON 结构:是否存在 "coordinates" 字段
boolean hasCoordinatesArray = fields.containsKey("coordinates");
if (hasCoordinatesArray) {
// 结构 1: 包含 "coordinates" 数组
ObjectReader reader = new ObjectMapper().readerFor(LngLat[].class);
longLatArray = reader.readValue(fields.get("coordinates")); // 反序列化 "coordinates" 节点
} else {
// 结构 2: 包含 "longitude" 和 "latitude" 字段
JsonNode longitudeNode = fields.get("longitude");
JsonNode latitudeNode = fields.get("latitude");
// 检查字段是否存在以避免 NullPointerException
if (longitudeNode == null || latitudeNode == null) {
throw new IOException("Missing 'longitude' or 'latitude' fields for single LngLat representation.");
}
longLatArray = new LngLat[]{
new LngLat(
longitudeNode.asDouble(),
latitudeNode.asDouble()
)
};
}
return new NoFlyZone(longLatArray);
}
@Override
public String toString() {
return "NoFlyZone{coordinates=" + java.util.Arrays.toString(coordinates) + "}";
}
} 在上述代码中:
- @JsonIgnoreProperties(ignoreUnknown = true) 用于忽略JSON中可能存在的,但在Java类中没有对应属性的字段(例如"name"),防止反序列化失败。
- @JsonCreator 标记了一个静态工厂方法getInstance,Jackson会调用此方法来构造NoFlyZone实例。
- getInstance方法接收Map
,这允许我们访问原始JSON的所有字段及其对应的JsonNode。 - 通过检查fields.containsKey("coordinates"),我们能判断当前JSON是属于哪种结构。
- 对于包含coordinates数组的结构,我们使用一个独立的ObjectMapper().readerFor(LngLat[].class)来反序列化coordinates对应的JsonNode。
- 对于包含longitude和latitude的结构,我们直接从JsonNode中提取双精度值,并创建一个单元素的LngLat数组。
现在,我们可以同时反序列化两种不同结构的JSON字符串:
public class PolymorphicDeserializationExample {
public static void main(String[] args) throws Exception {
String json1 = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]";
ObjectMapper mapper = new ObjectMapper();
List noFlyZones1 = mapper.readValue(json1, new TypeReference>() {});
System.out.println("Deserialized JSON 1 (Polymorphic): " + noFlyZones1);
List noFlyZones2 = mapper.readValue(json2, new TypeReference>() {});
System.out.println("Deserialized JSON 2 (Polymorphic): " + noFlyZones2);
}
}
输出示例:
Deserialized JSON 1 (Polymorphic): [NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]
Deserialized JSON 2 (Polymorphic): [NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.0]]}]总结与注意事项
- @JsonFormat(shape = JsonFormat.Shape.ARRAY): 这是处理将JSON数组直接映射到Java对象属性的简洁方法。它要求JSON数组的元素顺序与Java类中字段的声明顺序一致。适用于固定、单一的数组表示形式。
-
@JsonCreator与工厂方法: 当需要处理更复杂的、具有多态性的JSON结构时,@JsonCreator结合工厂方法提供了最大的灵活性。通过接收Map
,您可以在工厂方法内部根据JSON内容进行条件判断,并执行自定义的解析逻辑。 - @JsonIgnoreProperties(ignoreUnknown = true): 在处理多态或不确定字段的JSON时,这是一个非常有用的注解,可以防止Jackson因遇到Java类中没有对应属性的JSON字段而抛出异常。
-
TypeReference: 当反序列化泛型类型(如List
)时,需要使用new TypeReference - >() {}来提供完整的类型信息,以便Jackson正确处理。
- 错误处理: 在@JsonCreator工厂方法中,应考虑对缺失关键字段的情况进行错误处理,例如抛出IOException或其他业务异常,以确保数据的完整性和程序的健壮性。
-
性能考量: Map
的方式会先将整个JSON节点解析为JsonNode树,然后进行处理。对于非常大的JSON,这可能会有轻微的性能开销。但在大多数实际应用中,这种开销是可接受的,并且其带来的灵活性优势更为突出。
通过掌握这些Jackson的高级反序列化技巧,您可以有效地处理各种复杂和多变的JSON数据结构,使您的Java应用程序能够更健壮、更灵活地与外部系统进行数据交互。










