如何用Jackson将含嵌套字段与Map的JSON反序列化为复杂Java对象
JSON反序列化为OrderCount对象:Jackson实现、替代方案及MapStruct指南
现有类结构
@Data @SuperBuilder(toBuilder = true) @NoArgsConstructor public class OrderCount { private String entityId; private String entityName; private Integer totalTests; @Builder.Default private Map<String, Integer> products = new HashMap<>(); private List<OrderCount> childEntities; @JsonAnyGetter public Map<String, Integer> getProducts() { return products; } @JsonAnySetter public void setAdditionalProductProperty(String name, Integer value) { products.put(name, value); } }
待反序列化的JSON
{ "entityId": "01", "entityName": "entityName", "totalTests": 6, "childEntities": [ { "entityId": "01", "entityName": "entityName", "totalTests": 3, "productName1": 1, "productName2": 1, "productName_n": 1 }, { "entityId": "02", "entityName": "entityName_2", "totalTests": 3, "productName1": 1, "productName2": 1, "productName_n": 1, "childEntities": [] } ], "productName1": 2, "productName2": 2, "productName_n": 2 }
注:原JSON中
06、01这类带前导零的数字不符合JSON规范,建议改为普通整数(如6、1);若必须保留,需配置Jackson允许解析前导零数字。
字段映射规则
entityId↔ JSON的entityIdentityName↔ JSON的entityNametotalTests↔ JSON的totalTestschildEntities↔ JSON的childEntities- 其余所有字段自动存入
productsMap
一、Jackson 直接实现反序列化
你的OrderCount类已经通过@JsonAnyGetter和@JsonAnySetter完成了核心配置,直接用Jackson的ObjectMapper即可完成反序列化,无需额外修改类结构。
实现代码
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.DeserializationFeature; public class JacksonDeserializer { public static void main(String[] args) throws Exception { String json = "..."; // 替换为你的JSON字符串 ObjectMapper objectMapper = new ObjectMapper(); // 可选:如果JSON里有带前导零的数字,开启此配置 objectMapper.enable(DeserializationFeature.ALLOW_NUMERIC_LEADING_ZEROS); OrderCount orderCount = objectMapper.readValue(json, OrderCount.class); // 验证结果 System.out.println(orderCount.getEntityId()); System.out.println(orderCount.getProducts().get("productName1")); } }
原理说明
@JsonAnySetter会拦截所有未匹配到实体字段的JSON键值对,自动调用setAdditionalProductProperty方法将其存入productsMap- 嵌套的
childEntities会被Jackson递归反序列化为List<OrderCount>,内部的产品字段同样会被存入各自的productsMap
二、替代工具方案
1. Gson 实现
Gson没有直接对应@JsonAnySetter的注解,需要自定义TypeAdapter处理额外字段:
import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; public class OrderCountTypeAdapter extends TypeAdapter<OrderCount> { @Override public void write(JsonWriter out, OrderCount value) throws IOException { // 序列化逻辑按需实现 } @Override public OrderCount read(JsonReader in) throws IOException { OrderCount orderCount = new OrderCount(); Map<String, Integer> products = new HashMap<>(); in.beginObject(); while (in.hasNext()) { String name = in.nextName(); switch (name) { case "entityId": orderCount.setEntityId(in.nextString()); break; case "entityName": orderCount.setEntityName(in.nextString()); break; case "totalTests": orderCount.setTotalTests(in.nextInt()); break; case "childEntities": orderCount.setChildEntities(new Gson().fromJson(in, new TypeToken<List<OrderCount>>(){}.getType())); break; default: products.put(name, in.nextInt()); break; } } in.endObject(); orderCount.setProducts(products); return orderCount; } }
使用时注册适配器:
Gson gson = new GsonBuilder().registerTypeAdapter(OrderCount.class, new OrderCountTypeAdapter()).create(); OrderCount orderCount = gson.fromJson(json, OrderCount.class);
2. Fastjson 实现
Fastjson提供@ExtraField注解,可直接映射额外字段到Map:
修改OrderCount类的products字段:
@ExtraField private Map<String, Integer> products = new HashMap<>();
然后直接反序列化:
OrderCount orderCount = JSON.parseObject(json, OrderCount.class);
3. 手动解析(JsonNode)
如果不想依赖序列化库,可手动遍历JsonNode:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ManualParser { public static OrderCount parse(String json) throws Exception { ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(json); OrderCount orderCount = new OrderCount(); orderCount.setEntityId(root.get("entityId").asText()); orderCount.setEntityName(root.get("entityName").asText()); orderCount.setTotalTests(root.get("totalTests").asInt()); // 处理childEntities JsonNode childNode = root.get("childEntities"); if (childNode != null && childNode.isArray()) { List<OrderCount> children = new ArrayList<>(); for (JsonNode node : childNode) { children.add(parse(node.toString())); // 递归解析子节点 } orderCount.setChildEntities(children); } // 处理剩余字段 Map<String, Integer> products = new HashMap<>(); root.fields().forEachRemaining(entry -> { String key = entry.getKey(); if (!List.of("entityId", "entityName", "totalTests", "childEntities").contains(key)) { products.put(key, entry.getValue().asInt()); } }); orderCount.setProducts(products); return orderCount; } }
三、MapStruct 实现方案
MapStruct主要用于对象到对象的映射,这里需要先把JSON转成通用Map或JsonNode,再映射到OrderCount。
步骤1:定义Mapper接口
import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; import org.mapstruct.factory.Mappers; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Mapper public interface OrderCountMapper { OrderCountMapper INSTANCE = Mappers.getMapper(OrderCountMapper.class); @Mapping(target = "products", source = ".", qualifiedByName = "extractProducts") OrderCount mapFromMap(Map<String, Object> map); List<OrderCount> mapListFromMapList(List<Map<String, Object>> list); @Named("extractProducts") default Map<String, Integer> extractProducts(Map<String, Object> map) { // 过滤指定字段,剩余字段转Integer存入Map return map.entrySet().stream() .filter(entry -> !List.of("entityId", "entityName", "totalTests", "childEntities").contains(entry.getKey())) .collect(Collectors.toMap( Map.Entry::getKey, entry -> ((Number) entry.getValue()).intValue() )); } }
步骤2:结合Jackson转Map后映射
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; public class MapStructDemo { public static void main(String[] args) throws Exception { String json = "..."; // 替换为你的JSON字符串 ObjectMapper mapper = new ObjectMapper(); // 先把JSON转成Map<String, Object> Map<String, Object> jsonMap = mapper.readValue(json, new TypeReference<Map<String, Object>>() {}); OrderCount orderCount = OrderCountMapper.INSTANCE.mapFromMap(jsonMap); // 验证结果 System.out.println(orderCount.getProducts().get("productName1")); } }
说明
- MapStruct会自动处理
entityId、entityName、totalTests的映射 childEntities会被自动转成List<Map<String, Object>>,再通过mapListFromMapList递归映射为List<OrderCount>- 自定义的
extractProducts方法负责过滤指定字段,将剩余字段转为Map<String, Integer>存入products
内容的提问来源于stack exchange,提问作者Jerry
相关产品推荐
相关产品推荐

