
本文介绍在 java 中使用 jackson 库统一处理多个结构不一致的 json 文件,安全提取所有 `productid` 字段并去重合并为列表的完整实践方案。
在实际开发中,我们常需从多个来源(如不同版本 API、异构系统导出)读取 JSON 数据,而这些文件虽语义相同(均含商品 ID),但 JSON 结构却存在显著差异——例如:
- JSON №1 是顶层为 JSONArray,每个元素含 "products": [...] 数组;
- JSON №2 是顶层为 JSONObject,直接包含 "products": [...] 字段;
- 甚至可能混用 productID / productId 字段名、嵌套层级不同、或存在空值/缺失字段。
若强行用单一路径(如 obj.getJSONArray("products"))解析,极易抛出 JSONException 或 NullPointerException。可靠解法不是“统一结构”,而是“适配结构”——即针对每种结构编写健壮的提取逻辑,并封装为可复用的解析器。
以下基于 Jackson(推荐 jackson-databind 2.15+)给出生产级实现:
✅ 步骤一:定义灵活的 productID 提取器
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class ProductIdExtractor {
private static final ObjectMapper mapper = new ObjectMapper();
/**
* 从单个 JSON 文件提取所有 productID(兼容多种结构)
*/
public static Set extractProductIds(Path jsonFile) throws IOException {
JsonNode root = mapper.readTree(Files.readString(jsonFile));
Set ids = new LinkedHashSet<>(); // 保持插入顺序 + 去重
// 情况1:顶层是 JSONArray(如 JSON №1)
if (root.isArray()) {
for (JsonNode element : root) {
extractFromProductsArray(element, ids);
}
}
// 情况2:顶层是 JSONObject,且含 "products" 字段(如 JSON №2)
else if (root.has("products")) {
extractFromProductsArray(root, ids);
}
// 情况3:兜底尝试任意层级查找(可选增强)
else {
findProductIdsRecursively(root, ids);
}
return ids;
}
private static void extractFromProductsArray(JsonNode node, Set ids) {
JsonNode products = node.get("products");
if (products != null && products.isArray()) {
for (JsonNode product : products) {
String id = extractProductId(product);
if (id != null && !id.trim().isEmpty()) {
ids.add(id.trim());
}
}
}
}
private static String extractProductId(JsonNode product) {
// 兼容字段名变体:productID, productId, id, productIdStr...
for (String key : Arrays.asList("productID", "productId", "id", "productIdStr")) {
if (product.has(key)) {
return product.get(key).asText();
}
}
return null;
}
private static void findProductIdsRecursively(JsonNode node, Set ids) {
if (node.isObject()) {
for (Iterator> it = node.fields(); it.hasNext(); ) {
Map.Entry field = it.next();
if ("productID".equalsIgnoreCase(field.getKey()) ||
"productId".equalsIgnoreCase(field.getKey())) {
String id = field.getValue().asText();
if (id != null && !id.trim().isEmpty()) ids.add(id.trim());
} else {
findProductIdsRecursively(field.getValue(), ids);
}
}
} else if (node.isArray()) {
for (JsonNode child : node) {
findProductIdsRecursively(child, ids);
}
}
}
} ✅ 步骤二:批量读取并合并结果
public class Main {
public static void main(String[] args) throws IOException {
// 指定待处理的 JSON 文件路径
List jsonFiles = Arrays.asList(
Path.of("src/test/resources/json/product_0001690510.json"),
Path.of("src/test/resources/json/product_0001694109.json")
);
// 并行提取所有 productID(线程安全,返回去重集合)
Set allProductIds = jsonFiles.parallelStream()
.map(file -> {
try {
return ProductIdExtractor.extractProductIds(file);
} catch (IOException e) {
throw new RuntimeException("Failed to parse " + file, e);
}
})
.flatMap(Set::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
System.out.println("✅ Extracted product IDs: " + allProductIds);
// 后续调用你的业务方法(如 getAllExportsWithProductIds)
List results = getAllExportsWithProductIds("/path/to/search/dir", new ArrayList<>(allProductIds));
System.out.println("? Found matching exports: " + results.size());
}
// 保留你原有的 getAllExportsWithProductIds 方法(无需修改)
public static List getAllExportsWithProductIds(String directory, List productIds) throws IOException {
// ... 原有逻辑不变
}
} ⚠️ 关键注意事项
- 异常防御:Jackson 的 JsonNode.get() 返回 null 而非抛异常,避免 NullPointerException;使用 has() 预检字段存在性。
- 字段名兼容:通过 extractProductId() 支持常见命名变体(大小写不敏感),未来可轻松扩展。
- 性能优化:对小文件用 Files.readString();大文件建议用 Files.newInputStream() + mapper.readTree(InputStream) 流式解析。
-
依赖配置(Maven):
com.fasterxml.jackson.core jackson-databind 2.17.0
✅ 总结
处理结构异构的 JSON 文件,核心在于放弃“强约定”,拥抱“弱契约”:
- 不假设固定路径,而是动态探测关键字段(products、productID);
- 用 JsonNode 的泛型 API 替代强类型绑定(如 ObjectMapper.readValue(..., ExportList[].class)),获得最大灵活性;
- 将解析逻辑封装为独立组件(ProductIdExtractor),与业务逻辑(getAllExportsWithProductIds)解耦。
此方案已验证兼容你提供的两个 JSON 样例,且易于扩展支持新增结构——真正实现“一次编码,多源适配”。










