
本教程深入探讨了如何使用jackson库处理复杂的json反序列化场景。我们将学习如何通过`@jsonformat(shape = jsonformat.shape.array)`注解将2d json数组映射到java对象数组,以及如何利用`@jsoncreator`工厂方法和`map
在使用Jackson进行JSON反序列化时,我们通常期望JSON对象的字段能够直接映射到Java对象的属性。然而,当JSON结构变得复杂,例如一个Java对象被表示为JSON数组,或者同一个逻辑实体在不同的JSON输入中具有完全不同的结构时,标准的映射机制可能会遇到挑战。本文将详细介绍如何利用Jackson的强大功能来解决这些问题。
假设我们有一个表示经纬度坐标的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<NoFlyZone> noFlyZones = mapper.readValue(json1, new TypeReference<List<NoFlyZone>>() {});
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]]}]在某些情况下,同一个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<String, JsonNode>参数,从而允许我们检查原始JSON的所有字段,并根据其结构动态地构建NoFlyZone实例。
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<String, JsonNode> 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) + "}";
}
}在上述代码中:
现在,我们可以同时反序列化两种不同结构的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<NoFlyZone> noFlyZones1 = mapper.readValue(json1, new TypeReference<List<NoFlyZone>>() {});
System.out.println("Deserialized JSON 1 (Polymorphic): " + noFlyZones1);
List<NoFlyZone> noFlyZones2 = mapper.readValue(json2, new TypeReference<List<NoFlyZone>>() {});
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]]}]通过掌握这些Jackson的高级反序列化技巧,您可以有效地处理各种复杂和多变的JSON数据结构,使您的Java应用程序能够更健壮、更灵活地与外部系统进行数据交互。
以上就是Jackson反序列化复杂JSON结构:2D数组与多态映射指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号