You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson框架中ObjectMapper.readTree仅读取JSON文件首个对象的问题求助

解决Jackson readTree仅读取第一个JSON对象的问题

我明白你的困扰——当JSON文件里包含多个对象时,readTree只读取第一个确实让人头疼。先帮你理清问题根源,再给出具体的修复方案:

问题原因

如果你的exampleFile.json是每行一个独立JSON对象(也就是JSON Lines格式),而非一个顶层的JSON数组([{}, {}, ...]),那么mapper.readTree(file)只会解析第一个完整的JSON值,因为Jackson默认是处理单个JSON实体的。这时候就需要用JsonParser来逐个迭代读取文件里的所有对象。

如果你的文件本来应该是一个JSON数组,但readTree还是只拿到第一个对象,那可能是文件格式有问题(比如数组未正确闭合),不过先聚焦最常见的多独立对象场景。

修复方案

1. 用JsonParser迭代读取所有JSON对象

我们可以通过JsonParser遍历文件中的每个JSON对象,逐个处理后再写入新文件。同时优化你现有的字段过滤逻辑,用Set存储需要保留的字段,代码会更简洁易维护。

修改后的完整代码如下:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Predicate;

public class App {
    // 定义需要保留的字段集合,方便后续修改维护
    private static final Set<String> KEEP_FIELDS = new HashSet<>() {{
        add("artist");
        add("colors");
        add("multiverseId");
        add("scryfallId");
        add("manaCost");
        add("name");
        add("rarity");
        add("type");
        add("identifiers");
    }};

    public static void main(String[] args) throws IOException {
        File inputFile = new File("exampleFile.json").getAbsoluteFile();
        File outputFile = new File("masterCardList.json");
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT);

        // 使用try-with-resources自动关闭流,避免资源泄漏
        try (JsonParser parser = mapper.getFactory().createParser(inputFile);
             FileWriter writer = new FileWriter(outputFile)) {

            // 写入JSON数组开头(如果需要输出为数组格式)
            writer.write("[");
            boolean firstObject = true;

            while (!parser.isClosed()) {
                JsonToken token = parser.nextToken();
                // 定位到每个对象的起始标记
                if (JsonToken.START_OBJECT.equals(token)) {
                    // 读取当前JSON对象
                    ObjectNode root = mapper.readTree(parser);
                    // 清理不需要的字段
                    JsonCleaner jsonCleaner = new JsonCleaner(root, node -> node != null);
                    JsonNode cleanedNode = jsonCleaner.removeAll();

                    // 处理对象间的逗号分隔
                    if (!firstObject) {
                        writer.write(",");
                    }
                    firstObject = false;
                    // 写入清理后的对象
                    writer.write(mapper.writeValueAsString(cleanedNode));
                }
            }
            // 写入JSON数组结尾
            writer.write("]");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class JsonCleaner {
        private final JsonNode root;
        private final Predicate<JsonNode> toRemoveCondition;

        JsonCleaner(JsonNode node, Predicate<JsonNode> toRemoveCondition) {
            this.root = Objects.requireNonNull(node);
            this.toRemoveCondition = Objects.requireNonNull(toRemoveCondition);
        }

        public JsonNode removeAll() {
            process(root);
            return root;
        }

        private void process(JsonNode node) {
            if (node.isObject()) {
                ObjectNode object = (ObjectNode) node;
                Iterator<Map.Entry<String, JsonNode>> fields = object.fields();
                while (fields.hasNext()) {
                    Map.Entry<String, JsonNode> field = fields.next();
                    String fieldKey = field.getKey();
                    JsonNode valueToCheck = field.getValue();

                    // 用Set判断是否保留字段,比多个||判断更高效简洁
                    if (KEEP_FIELDS.contains(fieldKey)) {
                        process(valueToCheck);
                    } else {
                        fields.remove();
                    }
                }
            } else if (node.isArray()) {
                node.elements().forEachRemaining(this::process);
            }
        }
    }
}

2. 如果你的JSON文件本来就是顶层数组

如果exampleFile.json已经是[{}, {}, ...]格式,那readTree会返回一个ArrayNode,你的现有代码其实已经在遍历数组元素了(JsonCleaner的process方法处理数组时会遍历每个元素)。这时候问题可能出在写入文件的逻辑:你手动拼接字符串会导致格式错误,只需要修改写入部分为:

// 替换原有的写入逻辑
mapper.writeValue(cardFile, result);

Jackson会自动把整个数组格式化为正确的JSON写入文件,不需要手动拼接字符串。

额外优化点

  • 用Set存储需要保留的字段,比多个||判断更高效,也更容易维护
  • 使用try-with-resources语法自动关闭流,避免资源泄漏
  • 如果需要输出JSON Lines格式(每行一个对象),可以去掉数组的首尾括号,直接逐个写入对象即可

内容的提问来源于stack exchange,提问作者DavidRiley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 16:27:43