
在与外部服务进行数据交互时,我们经常会遇到json数据结构不完全固定的情况。尤其当json的顶级属性键是动态生成或随机变化的,例如"random1"、"nextrandom500"等,而其对应的值结构却是固定的对象时,使用jackson进行标准pojo(plain old java object)映射会遇到挑战。jackson默认期望json的键与pojo的属性名严格对应,一旦遇到未知属性,便会抛出com.fasterxml.jackson.databind.exc.unrecognizedpropertyexception异常。
例如,以下JSON结构中,random1、nextRandom500、random100500都是动态键:
{
"random1" : {
"name" : "john",
"lastName" : "johnson"
},
"nextRandom500" : {
"name" : "jack",
"lastName" : "jackson"
},
"random100500" : {
"name" : "jack",
"lastName" : "johnson"
}
}当尝试将上述JSON直接反序列化到一个包含Map<String, User>属性的UserResponse POJO时,如果JSON本身没有一个固定的包装键(如"users"),Jackson将无法识别"random1"等顶级键,从而导致反序列化失败。
Jackson在进行对象反序列化时,会尝试将JSON中的每个键映射到目标POJO类中的一个属性。如果JSON中存在POJO类中未定义的顶级键,且没有特殊配置(如@JsonIgnoreProperties(ignoreUnknown = true)),Jackson就会认为这是一个“无法识别的属性”,并抛出UnrecognizedPropertyException。
在上述示例中,如果我们的UserResponse POJO定义如下:
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; // Jackson会寻找名为"users"的键
@Data
@SuperBuilder(toBuilder=true)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class User {
private String name;
private String lastName;
}
}而实际的JSON数据是:
{
"random1" : { /* ... */ },
"nextRandom500" : { /* ... */ }
}Jackson会尝试将"random1"映射到UserResponse的某个属性,但UserResponse中只有一个users属性。由于"random1"与"users"不匹配,Jackson便会抛出异常。
当JSON的顶级键是动态的,但其值结构是固定的对象时,最直接有效的方法是将整个JSON反序列化为一个Map<String, T>类型。Jackson的TypeReference提供了一种在运行时保留泛型类型信息的方式,非常适合这种场景。
首先,我们需要定义动态键所对应的值对象User:
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;
}注意:这里不再需要一个外部的UserResponse类来包装这个Map,因为我们将直接把整个JSON看作一个Map。
使用ObjectMapper结合TypeReference将JSON字符串反序列化为Map<String, User>:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.util.Map;
public class JsonDeserializationExample {
public static void main(String[] args) throws IOException {
String jsonString = "{\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" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
// 配置以美化输出,非必需
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
// 使用TypeReference进行反序列化
Map<String, User> usersMap = objectMapper.readValue(jsonString, new TypeReference<Map<String, User>>(){});
System.out.println("反序列化成功,Map内容:");
usersMap.forEach((key, user) ->
System.out.println(" Key: " + key + ", User: " + user.getName() + " " + user.getLastName())
);
}
}解释: new TypeReference<Map<String, User>>(){}是一个匿名内部类,它允许Jackson在运行时获取到Map<String, User>的完整泛型类型信息,从而正确地将JSON中的每个顶级键值对映射到Map中的相应条目。
将Map<String, User>对象序列化回原始JSON格式同样简单,只需直接调用ObjectMapper的writeValueAsString方法:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class JsonSerializationExample {
public static void main(String[] args) throws IOException {
// 创建一个Map<String, User>对象
Map<String, User> usersMap = new HashMap<>();
usersMap.put("randomA", new User("Alice", "Smith"));
usersMap.put("randomB", new User("Bob", "Johnson"));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT); // 美化输出
// 序列化Map到JSON字符串
String serializedJson = objectMapper.writeValueAsString(usersMap);
System.out.println("序列化后的JSON:");
System.out.println(serializedJson);
}
}如果JSON的结构并非直接由动态键构成,而是包含一个固定的顶级键,该键的值是一个由动态键组成的Map,那么使用一个包装POJO是更合适的选择。
例如,如果你的JSON数据实际上是这样的:
{
"users": {
"random1" : {
"name" : "john",
"lastName" : "johnson"
},
"nextRandom500" : {
"name" : "jack",
"lastName" : "jackson"
},
"random100500" : {
"name" : "jack",
"lastName" : "johnson"
}
}
}在这种情况下,"users"是一个固定的顶级键,其值是一个Map<String, User>。
此时,你最初定义的UserResponse POJO是完全适用的:
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; // Jackson会寻找名为"users"的键
@Data
@SuperBuilder(toBuilder=true)
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class User {
private String name;
private String lastName;
}
}直接使用ObjectMapper对UserResponse.class进行反序列化和序列化:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class WrappedPojoExample {
public static void main(String[] args) throws IOException {
String wrappedJsonString = "{\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" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
// 反序列化
UserResponse userResponse = objectMapper.readValue(wrappedJsonString, UserResponse.class);
System.out.println("反序列化成功,UserResponse内容:");
userResponse.getUsers().forEach((key, user) ->
System.out.println(" Key: " + key + ", User: " + user.getName() + " " + user.getLastName())
);
// 序列化
UserResponse newResponse = new UserResponse();
Map<String, User> newUsers = new HashMap<>();
newUsers.put("newRandomX", new User("Charlie", "Brown"));
newResponse.setUsers(newUsers);
String serializedWrappedJson = objectMapper.writeValueAsString(newResponse);
System.out.println("\n序列化后的JSON:");
System.out.println(serializedWrappedJson);
}
}<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4</version> <!-- 或更高版本 -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.4</version> <!-- 或更高版本 -->
</dependency>
<!-- 如果使用Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version> <!-- 或更高版本 -->
<scope>provided</scope>
</dependency>通过掌握这些技巧,开发者可以灵活应对各种复杂的JSON数据结构,高效地进行序列化和反序列化操作。
以上就是Jackson处理带有动态属性键的JSON数据:序列化与反序列化教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号