首页 > Java > java教程 > 正文

使用Spring Boot和Jackson高效提取嵌套JSON数据

花韻仙語
发布: 2025-10-26 12:39:22
原创
567人浏览过

使用spring boot和jackson高效提取嵌套json数据

本文深入探讨了在Spring Boot应用中利用Jackson库处理复杂嵌套JSON数据的两种核心策略:数据绑定(Data Binding)和流式API(Streaming API/Tree Model)。文章通过具体代码示例,详细阐述了如何将嵌套JSON映射到Java对象,以及如何在结构未知或大型JSON场景下通过遍历JsonNode来提取特定信息,并提供了筛选分类数据的实现方法,旨在帮助开发者根据实际需求选择最合适的JSON处理方案。

1. 引言

在现代微服务架构中,Spring Boot应用程序经常需要与外部API进行交互,处理和解析复杂的JSON响应是常见的任务。当JSON数据包含多层嵌套结构时,如何高效、健壮地提取所需信息成为一个挑战。本文将重点介绍如何利用Jackson库,在Spring Boot环境中优雅地处理这类嵌套JSON数据,并提供两种主要方法:数据绑定和流式API(或称为树模型)。

2. 准备工作:Jackson依赖

Jackson是Spring Boot默认集成的JSON处理库,通常无需额外添加依赖。但如果项目是手动构建或需要特定版本,请确保pom.xml中包含以下Jackson相关依赖:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.4</version> <!-- 请使用最新稳定版本 -->
</dependency>
登录后复制

3. 方法一:Jackson数据绑定 (Data Binding)

对于结构相对固定且已知,或者数据量适中的JSON,数据绑定是首选方案。它将JSON直接映射到预定义的Java对象(POJO),提供了极佳的可读性和类型安全性。

3.1 定义POJO类

首先,根据JSON结构定义对应的Java类。对于给定的JSON示例:

[
    {
        "id": 1,
        "footwearList": [...],
        "clothingList": [...]
    },
    {
        "id": 2,
        "footwearList": [...],
        "clothingList": [...]
    }
]
登录后复制

其中footwearList和clothingList内部结构相同,可以抽象为一个Item类。而外部对象包含一个id和多个动态列表。

Item类:

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 + '\'' +
               '}';
    }
}
登录后复制

Store类 (处理动态列表键):

为了处理footwearList和clothingList这种键名不固定但值类型一致的结构,可以使用@JsonAnySetter和@JsonAnyGetter将它们映射到一个Map<String, List<Item>>中。

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) {
        items.put(key, value);
    }

    // 使用 @JsonAnyGetter 序列化回JSON(可选,取决于需求)
    @JsonAnyGetter
    public Map<String, List<Item>> getItems() {
        return items;
    }

    @Override
    public String toString() {
        return "Store{" +
               "id=" + id +
               ", items=" + items +
               '}';
    }
}
登录后复制

3.2 反序列化JSON

有了POJO类,就可以使用ObjectMapper进行反序列化。

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.stream.Collectors;

public class JsonDataBindingService {

    private final ObjectMapper objectMapper = new ObjectMapper();

    public List<Store> parseStores(String jsonString) throws Exception {
        // 反序列化JSON字符串为List<Store>
        return objectMapper.readValue(jsonString, new TypeReference<List<Store>>() {});
    }

    public List<Item> getFootwearItemsByCategory(List<Store> stores, String category) {
        return stores.stream()
                     .flatMap(store -> store.getItems().getOrDefault("footwearList", List.of()).stream())
                     .filter(item -> item.getCategory().equalsIgnoreCase(category))
                     .collect(Collectors.toList());
    }

    public List<Item> getClothingItemsByCategory(List<Store> stores, String category) {
        return stores.stream()
                     .flatMap(store -> store.getItems().getOrDefault("clothingList", List.of()).stream())
                     .filter(item -> item.getCategory().equalsIgnoreCase(category))
                     .collect(Collectors.toList());
    }

    public static void main(String[] args) throws Exception {
        String json = "[{\"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\"}]}]";

        JsonDataBindingService service = new JsonDataBindingService();
        List<Store> stores = service.parseStores(json);

        System.out.println("所有商店数据:");
        stores.forEach(System.out::println);

        // 提取所有男士鞋类
        List<Item> menFootwear = service.getFootwearItemsByCategory(stores, "men");
        System.out.println("\n所有男士鞋类:");
        menFootwear.forEach(System.out::println);

        // 提取所有女士服装
        List<Item> womenClothing = service.getClothingItemsByCategory(stores, "women");
        System.out.println("\n所有女士服装:");
        womenClothing.forEach(System.out::println);
    }
}
登录后复制

优点:

Find JSON Path Online
Find JSON Path Online

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

Find JSON Path Online 30
查看详情 Find JSON Path Online
  • 类型安全: 操作的是强类型Java对象,减少运行时错误。
  • 可读性高: 代码更简洁,易于理解和维护。
  • 开发效率: 一旦POJO定义完成,JSON处理变得非常简单。

注意事项:

  • 适用于JSON结构相对稳定且可预测的场景。
  • 对于非常大的JSON文件,一次性加载到内存可能导致内存消耗过高。

4. 方法二:Jackson流式API / 树模型 (Streaming API / Tree Model)

当JSON结构复杂、未知,或者需要处理超大型JSON文件以优化内存使用时,Jackson的流式API或树模型提供了更大的灵活性。树模型通过JsonNode表示JSON结构,允许开发者以类似DOM的方式遍历和查询数据。

4.1 使用JsonNode遍历

首先,将JSON字符串解析为JsonNode树。然后,可以遍历这棵树来查找特定的字段。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;

public class JsonTreeModelService {

    private final ObjectMapper objectMapper = new ObjectMapper();

    // 用于匹配包含 "footwear" 的键
    public static final Predicate<String> FOOTWEAR_PREDICATE = Pattern.compile("footwear", Pattern.CASE_INSENSITIVE).asPredicate();
    // 用于匹配包含 "clothing" 的键
    public static final Predicate<String> CLOTHING_PREDICATE = Pattern.compile("clothing", Pattern.CASE_INSENSITIVE).asPredicate();

    /**
     * 将JsonNode转换为Item对象
     */
    private Item nodeToItem(JsonNode node) {
        // 假设每个Item节点都有id, name, category字段
        int id = node.has("id") ? node.get("id").asInt() : 0;
        String name = node.has("name") ? node.get("name").asText() : null;
        String category = node.has("category") ? node.get("category").asText() : null;
        return new Item(id, name, category);
    }

    /**
     * 从JSON字符串中提取特定列表(如footwearList, clothingList)中的Item对象
     * 并可选择按category过滤。
     *
     * @param jsonString 原始JSON字符串
     * @param keyPredicate 用于匹配列表键的谓词 (例如 FOOTWEAR_PREDICATE, CLOTHING_PREDICATE)
     * @param filterCategory 可选的分类过滤字符串,如果为null或空则不进行过滤
     * @return 匹配的Item列表
     * @throws Exception 如果JSON解析失败
     */
    public List<Item> extractItemsFromNestedLists(String jsonString, Predicate<String> keyPredicate, String filterCategory) throws Exception {
        JsonNode rootNode = objectMapper.readTree(jsonString);
        List<Item> extractedItems = new ArrayList<>();

        if (rootNode.isArray()) {
            for (JsonNode storeNode : rootNode) {
                // 遍历Store节点下的所有字段
                storeNode.fields().forEachRemaining(entry -> {
                    String fieldName = entry.getKey();
                    JsonNode fieldNode = entry.getValue();

                    if (keyPredicate.test(fieldName) && fieldNode.isArray()) {
                        for (JsonNode itemNode : fieldNode) {
                            Item item = nodeToItem(itemNode);
                            // 应用分类过滤
                            if (filterCategory == null || filterCategory.isEmpty() ||
                                item.getCategory().equalsIgnoreCase(filterCategory)) {
                                extractedItems.add(item);
                            }
                        }
                    }
                });
            }
        }
        return extractedItems;
    }

    public static void main(String[] args) throws Exception {
        String json = "[{\"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\"}]}]";

        JsonTreeModelService service = new JsonTreeModelService();

        // 提取所有鞋类 (不按分类过滤)
        List<Item> allFootwear = service.extractItemsFromNestedLists(json, FOOTWEAR_PREDICATE, null);
        System.out.println("所有鞋类:");
        allFootwear.forEach(System.out::println);

        // 提取所有女士服装
        List<Item> womenClothing = service.extractItemsFromNestedLists(json, CLOTHING_PREDICATE, "women");
        System.out.println("\n所有女士服装:");
        womenClothing.forEach(System.out::println);

        // 提取所有男士鞋类
        List<Item> menFootwear = service.extractItemsFromNestedLists(json, FOOTWEAR_PREDICATE, "men");
        System.out.println("\n所有男士鞋类:");
        menFootwear.forEach(System.out::println);
    }
}
登录后复制

优点:

  • 灵活性高: 无需预先定义所有POJO,适用于JSON结构不确定或频繁变化的场景。
  • 内存效率: 可以按需读取和处理JSON节点,避免一次性加载整个大文件到内存。
  • 强大的查询能力: 结合JsonNode的各种方法(get(), path(), elements(), fields()等),可以实现复杂的JSON查询。

注意事项:

  • 代码相对更冗长,需要手动处理类型转换和空值检查。
  • 可读性不如数据绑定。

5. 总结与选择建议

  • 数据绑定 (Data Binding):

    • 推荐场景: JSON结构稳定、已知,数据量适中,追求代码简洁、类型安全和开发效率。这是大多数日常开发任务的首选。
    • 优点: 易于理解和维护,自动类型转换,减少样板代码。
    • 缺点: 对于结构高度动态或超大型JSON文件可能不适用。
  • 流式API / 树模型 (Streaming API / Tree Model):

    • 推荐场景: JSON结构高度动态、未知,或者需要处理内存中无法完全容纳的超大型JSON文件,对性能和内存使用有严格要求。
    • 优点: 极高的灵活性,内存效率高,适用于复杂查询。
    • 缺点: 代码相对复杂,需要手动处理数据类型和空值,可读性较低。

在Spring Boot应用中,通常会优先考虑使用Jackson的数据绑定功能,因为它能带来更高的开发效率和代码可维护性。只有在遇到数据绑定难以处理的特殊情况(如JSON结构极度不规则或文件过大)时,才考虑使用更底层的流式API或树模型。

通过以上两种方法,开发者可以根据具体需求,灵活选择Jackson提供的强大功能,高效地在Spring Boot应用中处理各种嵌套JSON数据。

以上就是使用Spring Boot和Jackson高效提取嵌套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号