
在处理外部服务返回的JSON数据时,我们有时会遇到顶级属性键名是动态生成的情况。例如,JSON结构可能如下所示,其中 "random1"、"nextRandom500" 等键是不可预测的:
{
"random1" : {
"name" : "john",
"lastName" : "johnson"
},
"nextRandom500" : {
"name" : "jack",
"lastName" : "jackson"
},
"random100500" : {
"name" : "jack",
"lastName" : "johnson"
}
}直接将这种结构反序列化到仅包含固定字段的POJO(如 UserResponse 仅有一个 users 字段)会导致 UnrecognizedPropertyException 错误,因为Jackson无法找到与动态键名匹配的POJO属性。为了正确处理这种场景,Jackson提供了两种主要的策略。
如果允许修改JSON的结构,或者可以在客户端对JSON进行预处理,使其包含一个固定的根属性来包裹所有动态键名,那么可以使用一个包含 Map<String, POJO> 字段的包装POJO。
将原始JSON调整为如下形式,引入一个固定的根键(例如 "users")来包含所有动态键名及其对应的值:
{
"users": {
"random1" : {
"name" : "john",
"lastName" : "johnson"
},
"nextRandom500" : {
"name" : "jack",
"lastName" : "jackson"
},
"random100500" : {
"name" : "jack",
"lastName" : "johnson"
}
}
}首先,定义内部的 User POJO,它代表了每个动态键名对应的值结构:
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder(toBuilder=true)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
private String name;
private String lastName;
}然后,定义一个包装POJO UserResponse,其中包含一个 Map<String, User> 字段,其键名为 "users",与调整后的JSON结构中的根键对应:
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Map;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder(toBuilder=true)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserResponse {
private Map<String, User> users; // 这里的"users"字段将映射到JSON中的"users"键
}使用 ObjectMapper 进行反序列化和序列化操作将变得直接:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class JsonProcessingExample {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
// 示例JSON (调整后的结构)
String jsonWithWrapper = "{\n" +
" \"users\": {\n" +
" \"random1\" : {\n" +
" \"name\" : \"john\",\n" +
" \"lastName\" : \"johnson\"\n" +
" },\n" +
" \"nextRandom500\" : {\n" +
" \"name\" : \"jack\",\n" +
" \"lastName\" : \"jackson\"\n" +
" },\n" +
" \"random100500\" : {\n" +
" \"name\" : \"jack\",\n" +
" \"lastName\" : \"johnson\"\n" +
" }\n" +
" }\n" +
"}";
// 反序列化
UserResponse userResponse = objectMapper.readValue(jsonWithWrapper, UserResponse.class);
System.out.println("Deserialized UserResponse:");
userResponse.getUsers().forEach((key, user) ->
System.out.println(" Key: " + key + ", Name: " + user.getName() + ", Last Name: " + user.getLastName()));
// 序列化回JSON
String serializedJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(userResponse);
System.out.println("\nSerialized JSON from UserResponse:");
System.out.println(serializedJson);
// 构建一个UserResponse对象进行序列化
UserResponse newUserResponse = new UserResponse();
Map<String, User> newUsers = new HashMap<>();
newUsers.put("newRandomKey1", User.builder().name("Alice").lastName("Smith").build());
newUsers.put("newRandomKey2", User.builder().name("Bob").lastName("White").build());
newUserResponse.setUsers(newUsers);
String newSerializedJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(newUserResponse);
System.out.println("\nSerialized JSON from new UserResponse object:");
System.out.println(newSerializedJson);
}
}如果无法修改JSON的原始结构,或者动态键名本身就是JSON的根,那么可以直接将JSON反序列化为一个 Map<String, POJO> 类型。
在这种策略下,我们只需要定义 User POJO即可,不需要 UserResponse 包装类:
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder(toBuilder=true)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
private String name;
private String lastName;
}由于 Map<String, User> 是一个泛型类型,Jackson在反序列化时需要 TypeReference 来正确推断类型信息。
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class JsonProcessingDirectMapExample {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
// 示例JSON (原始结构)
String originalJson = "{\n" +
" \"random1\" : {\n" +
" \"name\" : \"john\",\n" +
" \"lastName\" : \"johnson\"\n" +
" },\n" +
" \"nextRandom500\" : {\n" +
" \"name\" : \"jack\",\n" +
" \"lastName\" : \"jackson\"\n" +
" },\n" +
" \"random100500\" : {\n" +
" \"name\" : \"jack\",\n" +
" \"lastName\" : \"johnson\"\n" +
" } \n" +
"}";
// 反序列化为 Map<String, User>
Map<String, User> usersMap = objectMapper.readValue(originalJson,
new TypeReference<Map<String, User>>() {});
System.out.println("Deserialized Map<String, User>:");
usersMap.forEach((key, user) ->
System.out.println(" Key: " + key + ", Name: " + user.getName() + ", Last Name: " + user.getLastName()));
// 序列化回JSON
String serializedJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(usersMap);
System.out.println("\nSerialized JSON from Map<String, User>:");
System.out.println(serializedJson);
// 构建一个Map<String, User>对象进行序列化
Map<String, User> newUsersMap = new HashMap<>();
newUsersMap.put("anotherRandomKeyA", User.builder().name("Charlie").lastName("Brown").build());
newUsersMap.put("anotherRandomKeyB", User.builder().name("Diana").lastName("Prince").build());
String newSerializedJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(newUsersMap);
System.out.println("\nSerialized JSON from new Map<String, User> object:");
System.out.println(newSerializedJson);
}
}通过理解并应用上述两种策略,开发者可以灵活高效地使用Jackson库处理各种包含动态键名的JSON数据,实现与Java POJO之间的无缝序列化和反序列化。
以上就是深入理解Jackson处理动态键名JSON的策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号