首页 > Java > java教程 > 正文

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

花韻仙語
发布: 2025-10-26 12:08:16
原创
765人浏览过

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);
    }
}
登录后复制

输出示例:

AIBox 一站式AI创作平台
AIBox 一站式AI创作平台

AIBox365一站式AI创作平台,支持ChatGPT、GPT4、Claue3、Gemini、Midjourney等国内外大模型

AIBox 一站式AI创作平台 217
查看详情 AIBox 一站式AI创作平台
所有提取的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 等注解,能够以最简洁的方式解决嵌套和动态键的问题。

以上就是Spring Boot中高效提取嵌套JSON数据的策略的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号