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
相关产品推荐
相关产品推荐

