Spring Boot中高效提取嵌套JSON数据的策略

Spring Boot中高效提取嵌套JSON数据的策略

本教程详细探讨了在spring boot应用中如何高效地从复杂嵌套json结构中提取特定数据。我们将重点介绍jackson库的两种核心方法:jackson streaming api,适用于处理大型或结构动态的json,以及jackson data binding,适用于将JSon映射到预定义java对象。文章将提供详细的代码示例和实现步骤,并讨论如何进一步过滤提取出的数据,例如按类别筛选。

spring boot应用程序中处理来自api调用json数据是常见的任务,尤其当JSON结构包含多层嵌套时,如何高效且优雅地提取所需信息成为一个关键问题。本文将深入探讨使用Jackson库来解决这一挑战的两种主要方法:Jackson streaming API和Jackson Data Binding,并提供具体的代码示例。

1. 准备工作

在Spring Boot项目中,Jackson是默认的json处理库,因此通常无需额外添加依赖。但如果需要确保版本或解决特定问题,可以检查 pom.xml 中是否包含以下依赖:

<dependency>     <groupId>com.fasterxml.jackson.core</groupId>     <artifactId>jackson-databind</artifactId> </dependency>

假设我们有以下JSON数据,其中包含嵌套的 footwearList 和 clothingList:

[     {         "id": 1,         "footwearList": [             {                 "id": 1,                 "name": "sandals",                 "category": "men"             },             {                 "id": 3,                 "name": "sandals",                 "category": "women"             }         ],         "clothingList": [             {                 "id": 1,                 "name": "t-shirt",                 "category": "men"             },             {                 "id": 3,                 "name": "tshirt",                 "category": "women"             }         ]     },     {         "id": 2,         "footwearList": [             {                 "id": 2,                 "name": "shoes",                 "category": "men"             },             {                 "id": 4,                 "name": "shoes",                 "category": "women"             }         ],         "clothingList": [             {                 "id": 2,                 "name": "shirt",                 "category": "men"             },             {                 "id": 4,                 "name": "shirt",                 "category": "women"             }         ]     } ]

我们的目标是从这个JSON中提取 footwearList 和 clothingList 中的 Item 对象,并可能进一步按 category 进行筛选。

2. 使用Jackson Streaming API

当JSON结构非常庞大、键名动态不固定,或者我们不希望将整个JSON完全映射为Java对象时,Jackson Streaming API提供了一种高效的迭代方式。它允许我们遍历JSON树,按需提取数据。

首先,定义一个 Item 类来表示 footwearList 和 clothingList 中的元素:

public class Item {     private int id;     private String name;     private String category;      // 无参构造函数     public Item() {}      // 带参构造函数     public Item(int id, String name, String category) {         this.id = id;         this.name = name;         this.category = category;     }      // Getters and Setters     public int getId() { return id; }     public void setId(int id) { this.id = id; }     public String getName() { return name; }     public void setName(String name) { this.name = name; }     public String getCategory() { return category; }     public void setCategory(String category) { this.category = category; }      @Override     public String toString() {         return "Item{id=" + id + ", name='" + name + "', category='" + category + "'}";     } }

接下来,我们创建一个辅助方法将 Jsonnode 转换为 Item 对象:

import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper;  public class JsonExtractor {      private static final Objectmapper MAPPER = new ObjectMapper();      // 辅助方法:将JsonNode转换为Item对象     public static Item nodeToItem(JsonNode node) {         try {             return MAPPER.treeToValue(node, Item.class);         } catch (com.fasterxml.jackson.core.JsonProcessingException e) {             System.err.println("Error converting JsonNode to Item: " + e.getMessage());             return null;         }     }     // ... 其他方法 }

现在,我们可以编写逻辑来遍历JSON并提取嵌套列表中的 Item:

import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; import java.util.regex.Pattern; import java.util.function.Predicate; import java.util.stream.StreamSupport; import java.util.Collections; // 导入Collections  public class JsonExtractor {      private static final ObjectMapper MAPPER = new ObjectMapper();     // 定义一个正则表达式谓词,用于匹配包含"footwear"或"clothing"的字段名     public static final Predicate<String> TARGET_LIST_PREDICATE = Pattern.compile("footwear|clothing").asPredicate();      // 辅助方法:将JsonNode转换为Item对象 (同上)     public static Item nodeToItem(JsonNode node) {         try {             return MAPPER.treeToValue(node, Item.class);         } catch (com.fasterxml.jackson.core.JsonProcessingException e) {             System.err.println("Error converting JsonNode to Item: " + e.getMessage());             return null;         }     }      public List<Item> extractNestedItems(String json) {         try {             JsonNode rootNode = MAPPER.readTree(json);             if (!rootNode.isArray()) {                 System.err.println("Root JSON node is not an array.");                 return Collections.emptyList();             }              return StreamSupport.stream(rootNode.spliterator(), false) // 遍历根数组的每个对象                 .<JsonNode>mapMulti((node, consumer) -> { // 对于每个对象                     node.fields().forEachRemaining(entry -> { // 遍历其所有字段                         if (TARGET_LIST_PREDICATE.test(entry.getKey()) && entry.getValue().isArray()) {                             // 如果字段名匹配且其值为数组,则将数组中的每个元素传递给consumer                             entry.getValue().forEach(consumer);                         }                     });                 })                 .map(JsonExtractor::nodeToItem) // 将每个JsonNode转换为Item对象                 .toList(); // 收集结果         } catch (com.fasterxml.jackson.core.JsonProcessingException e) {             System.err.println("Error parsing JSON: " + e.getMessage());             return Collections.emptyList();         }     }      // 示例用法     public static void main(String[] args) {         String json = "[n" +                 "    {n" +                 "        "id": 1,n" +                 "        "footwearList": [n" +                 "            { "id": 1, "name": "sandals", "category": "men" },n" +                 "            { "id": 3, "name": "sandals", "category": "women" }n" +                 "        ],n" +                 "        "clothingList": [n" +                 "            { "id": 1, "name": "t-shirt", "category": "men" },n" +                 "            { "id": 3, "name": "tshirt", "category": "women" }n" +                 "        ]n" +                 "    },n" +                 "    {n" +                 "        "id": 2,n" +                 "        "footwearList": [n" +                 "            { "id": 2, "name": "shoes", "category": "men" },n" +                 "            { "id": 4, "name": "shoes", "category": "women" }n" +                 "        ],n" +                 "        "clothingList": [n" +                 "            { "id": 2, "name": "shirt", "category": "men" },n" +                 "            { "id": 4, "name": "shirt", "category": "women" }n" +                 "        ]n" +                 "    }n" +                 "]";          JsonExtractor extractor = new JsonExtractor();         List<Item> allItems = extractor.extractNestedItems(json);         System.out.println("所有提取的Item:");         allItems.forEach(System.out::println);          // 进一步按类别过滤         List<Item> menItems = allItems.stream()                                       .filter(item -> "men".equals(item.getCategory()))                                       .toList();         System.out.println("n按类别'men'过滤的Item:");         menItems.forEach(System.out::println);     } }

输出示例:

Spring Boot中高效提取嵌套JSON数据的策略

Find JSON Path Online

Easily find JSON paths within JSON objects using our intuitive Json Path Finder

Spring Boot中高效提取嵌套JSON数据的策略30

查看详情 Spring Boot中高效提取嵌套JSON数据的策略

所有提取的Item: Item{id=1, name='sandals', category='men'} Item{id=3, name='sandals', category='women'} Item{id=1, name='t-shirt', category='men'} Item{id=3, name='tshirt', category='women'} Item{id=2, name='shoes', category='men'} Item{id=4, name='shoes', category='women'} Item{id=2, name='shirt', category='men'} Item{id=4, name='shirt', category='women'}  按类别'men'过滤的Item: Item{id=1, name='sandals', category='men'} Item{id=1, name='t-shirt', category='men'} Item{id=2, name='shoes', category='men'} Item{id=2, name='shirt', category='men'}

这种方法非常灵活,即使JSON的顶层结构或嵌套列表的键名发生变化,只要我们调整 TARGET_LIST_PREDICATE,代码仍然可以工作。

3. 使用Jackson Data Binding

对于结构相对固定且已知其模式的JSON数据,Jackson Data Binding是更推荐和简洁的方法。它允许我们将JSON直接映射到Java对象(POJO),极大地简化了数据访问

首先,定义一个 Store 类来表示JSON中的顶层对象。由于 footwearList 和 clothingList 的键是动态的,我们可以使用 Map<String, List<Item>> 结合 @JsonAnySetter 和 @JsonAnyGetter 来处理。

import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import java.util.HashMap; import java.util.List; import java.util.Map;  public class Store {     private int id;     private Map<String, List<Item>> items = new HashMap<>(); // 用于存储动态的列表      // 无参构造函数     public Store() {}      // Getters and Setters for id     public int getId() { return id; }     public void setId(int id) { this.id = id; }      // @JsonAnySetter 用于反序列化时捕获未映射的键值对     @JsonAnySetter     public void readStore(String key, List<Item> value) {         this.items.put(key, value);     }      // @JsonAnyGetter 用于序列化时将Map中的内容作为顶级字段输出     // 注意:这里的实现是为了演示,实际序列化时可能需要更精细的控制     @JsonAnyGetter     public Map<String, List<Item>> getItems() {         return items;     }      @Override     public String toString() {         return "Store{id=" + id + ", items=" + items + '}';     } }

现在,我们可以使用 ObjectMapper 将整个JSON字符串反序列化为 List<Store>:

import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Collections; import java.util.List; import java.util.stream.Collectors;  public class JsonDataBinder {      private static final ObjectMapper MAPPER = new ObjectMapper();      public List<Store> parseJsonToStores(String json) {         try {             return MAPPER.readValue(json, new TypeReference<List<Store>>() {});         } catch (com.fasterxml.jackson.core.JsonProcessingException e) {             System.err.println("Error parsing JSON to Store list: " + e.getMessage());             return Collections.emptyList();         }     }      public static void main(String[] args) {         String json = "[n" +                 "    {n" +                 "        "id": 1,n" +                 "        "footwearList": [n" +                 "            { "id": 1, "name": "sandals", "category": "men" },n" +                 "            { "id": 3, "name": "sandals", "category": "women" }n" +                 "        ],n" +                 "        "clothingList": [n" +                 "            { "id": 1, "name": "t-shirt", "category": "men" },n" +                 "            { "id": 3, "name": "tshirt", "category": "women" }n" +                 "        ]n" +                 "    },n" +                 "    {n" +                 "        "id": 2,n" +                 "        "footwearList": [n" +                 "            { "id": 2, "name": "shoes", "category": "men" },n" +                 "            { "id": 4, "name": "shoes", "category": "women" }n" +                 "        ],n" +                 "        "clothingList": [n" +                 "            { "id": 2, "name": "shirt", "category": "men" },n" +                 "            { "id": 4, "name": "shirt", "category": "women" }n" +                 "        ]n" +                 "    }n" +                 "]";          JsonDataBinder binder = new JsonDataBinder();         List<Store> stores = binder.parseJsonToStores(json);          System.out.println("所有解析的Store对象:");         stores.forEach(System.out::println);          // 提取所有鞋类商品         List<Item> allFootwear = stores.stream()                                        .flatMap(store -> store.getItems().getOrDefault("footwearList", Collections.emptyList()).stream())                                        .collect(Collectors.toList());         System.out.println("n所有鞋类商品:");         allFootwear.forEach(System.out::println);          // 提取所有男士服装         List<Item> menClothing = stores.stream()                                        .flatMap(store -> store.getItems().getOrDefault("clothingList", Collections.emptyList()).stream())                                        .filter(item -> "men".equals(item.getCategory()))                                        .collect(Collectors.toList());         System.out.println("n所有男士服装:");         menClothing.forEach(System.out::println);     } }

输出示例:

所有解析的Store对象: Store{id=1, items={footwearList=[Item{id=1, name='sandals', category='men'}, Item{id=3, name='sandals', category='women'}], clothingList=[Item{id=1, name='t-shirt', category='men'}, Item{id=3, name='tshirt', category='women'}]}} Store{id=2, items={footwearList=[Item{id=2, name='shoes', category='men'}, Item{id=4, name='shoes', category='women'}], clothingList=[Item{id=2, name='shirt', category='men'}, Item{id=4, name='shirt', category='women'}]}}  所有鞋类商品: Item{id=1, name='sandals', category='men'} Item{id=3, name='sandals', category='women'} Item{id=2, name='shoes', category='men'} Item{id=4, name='shoes', category='women'}  所有男士服装: Item{id=1, name='t-shirt', category='men'} Item{id=2, name='shirt', category='men'}

Data Binding方法将整个JSON结构映射为强类型Java对象,使得后续的数据访问和操作(如使用Java Stream API进行过滤)变得非常直观和类型安全。

4. 注意事项与总结

  • 选择合适的解析策略:
    • Jackson Streaming API: 适用于JSON结构不固定、键名动态、数据量非常大(避免一次性加载所有数据到内存)的场景。它提供了更细粒度的控制,但代码相对复杂。
    • Jackson Data Binding: 适用于JSON结构相对稳定且可以预先定义POJO的场景。它提供了更高的开发效率和代码可读性,是大多数Spring Boot应用的首选。
  • 错误处理: 在实际应用中,始终要包含 try-catch 块来处理 JsonProcessingException(或其子类 IOException),以优雅地处理JSON解析过程中可能出现的错误。
  • 性能: 对于中小型JSON,Data Binding的性能通常足够好。对于超大型JSON,Streaming API可能提供更好的内存效率。
  • JSONPath: 虽然原问题中提到了 JsonPath,它是一个强大的JSON查询语言。但在Spring Boot的Jackson生态系统中,通常可以直接利用Jackson本身的API来实现大部分需求,而无需引入额外的 JsonPath 依赖,从而保持项目依赖的简洁性。

通过掌握Jackson Streaming API和Data Binding这两种方法,开发者可以根据具体需求,在Spring Boot中灵活高效地处理各种复杂嵌套的JSON数据。在大多数情况下,Data Binding结合 @JsonAnySetter 等注解,能够以最简洁的方式解决嵌套和动态键的问题。

暂无评论

发送评论 编辑评论


				
上一篇
下一篇
text=ZqhQzanResources