如何在Java中从JSON文件中删除键值对

3

我想知道有没有人可以帮助我或提示我如何在Java中编辑附加的虚拟JSON文件。

正如您所看到的,我有一个包含许多值和遵循相同模式的子级的头对象。

我想知道是否有一种方法可以删除所有值为-1的键。

dummbyJsonFile

以下是我尝试的基于许多网站使用的 jackson

try {
            // create object mapper instance
            ObjectMapper mapper = new ObjectMapper();

            // convert JSON file to map
            Map<?, ?> map = mapper.readValue(Paths.get("test.json").toFile(), Map.class);

            // print map entries
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                isInteger = main.isObjectInteger(entry.getValue());

                
//              System.out.println("if value is all: " + entry.getKey() + "=" + entry.getValue());
//              

上述代码将显示文件的结构,但我的问题是如何达到孩子节点中的-1值并将其删除。
使用.getClass和.simpleName方法,我知道它是一个ArrayList,但我不知道如何搜索它。
非常感谢您的帮助!

1
不要在读取时使用像Map这样的类型绑定,因为无法保证在写入时得到相同的结构。使用JsonNode或Jackson用于JSON树表示的任何内容,递归地遍历JSON对象和数组,找到要删除的元素,然后简单地将它们全部删除即可。就是这样。 - terrorrussia-keeps-killing
1
请在提问时不要上传代码或错误的图片。 - Andreas
如果不需要使用Jackson,您可以尝试使用Gson库。对我来说,它更加简单易用。只需创建包含所需JSON字段的类,然后Gson将其解析为您的类对象。 - stuck
2个回答

4

Jackson中,您可以将整个JSON载荷读取为JsonNode并迭代所有属性以检查给定条件。 如果满足条件,则可以删除给定字段。 为此,您需要实现递归方法。 请参阅以下示例:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;

public class JsonRemoveSomeFieldsApp {

    public static void main(String[] args) throws IOException {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        JsonNode root = mapper.readTree(jsonFile);

        JsonCleaner jsonCleaner = new JsonCleaner(root, (node) -> node.isNumber() && node.numberValue().intValue() == -1);
        JsonNode result = jsonCleaner.removeAll();

        // write to file
        mapper.writeValue(System.out, result);
    }
}

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();
                JsonNode valueToCheck = field.getValue();
                if (valueToCheck.isContainerNode()) {
                    process(valueToCheck);
                } else if (toRemoveCondition.test(valueToCheck)) {
                    fields.remove();
                }
            }
        } else if (node.isArray()) {
            ArrayNode array = (ArrayNode) node;
            array.elements().forEachRemaining(this::process);
        }
    }
}

对于下面的 JSON 负载:

{
  "name": "Head",
  "missed": -1,
  "covered": -1,
  "children": [
    {
      "name": "project1",
      "missed": -1,
      "covered": -1,
      "children": [
        {
          "name": "project1",
          "missed": 10,
          "covered": 11
        }
      ]
    },
    {
      "name": "project1",
      "missed": -1,
      "covered": 12,
      "children": [
        {
          "name": "project1",
          "missed": 10,
          "covered": -1
        }
      ]
    }
  ]
}

上面的代码输出:

{
  "name" : "Head",
  "children" : [ {
    "name" : "project1",
    "children" : [ {
      "name" : "project1",
      "missed" : 10,
      "covered" : 11
    } ]
  }, {
    "name" : "project1",
    "covered" : 12,
    "children" : [ {
      "name" : "project1",
      "missed" : 10
    } ]
  } ]
}

参见:


1
谢谢您的解释和示例,我会尝试一下! - Anika

2
有两种主要的技术来解析和生成JSON数据(以及许多其他格式,如XML等):对象映射和事件/令牌/流导向处理。第二种方法对于许多情况,包括过滤,是最好的方法。具体优点如下:
  • 不需要将整个文件/数据完全加载到内存中,可以处理大量数据而无需担心内存问题
  • 速度更快,尤其是对于大文件
  • 可以很容易地使用此模式实现任何自定义类型/转换规则
Gson和Jackson都支持流导向处理。为了说明这个想法,这里举一个例子,使用一个小型的解析器/生成器https://github.com/anatolygudkov/green-jelly
import org.green.jelly.AppendableWriter;
import org.green.jelly.JsonBufferedWriter;
import org.green.jelly.JsonEventPump;
import org.green.jelly.JsonNumber;
import org.green.jelly.JsonParser;

import java.io.StringWriter;

public class UpdateMyJson {
    private static final String jsonToUpdate = "{\n" +
            "\"name\": \"Head\",\n" +
            "\"missed\": -1,\n" +
            "\"children\": [\n" +
            "    {\n" +
            "        \"name\": \"project1\",\n" +
            "        \"fixes\": 0,\n" +
            "        \"commits\": -1,\n" +
            "    },\n" +
            "    {\n" +
            "        \"name\": \"project2\",\n" +
            "        \"fixes\": 20,\n" +
            "        \"commits\": 5,\n" +
            "    }\n" +
            "]\n" +
            "}";

    public static void main(String[] args) {
        final StringWriter result = new StringWriter(); // you can use FileWriter

        final JsonParser parser = new JsonParser();
        parser.setListener(new MyJsonUpdater(new AppendableWriter<>(result)));
        parser.parseAndEoj(jsonToUpdate); // if you read a file with a buffer,
        // to don't load the whole file into memory,
        // call parse() several times (part by part) in a loop until EOF
        // and then call .eoj()

        System.out.println(result);
    }

    static class MyJsonUpdater extends JsonEventPump {
        MyJsonUpdater(final JsonBufferedWriter output) {
            super(output);
        }

        @Override
        public boolean onNumberValue(final JsonNumber number) {
            if (number.mantissa() == -1 && number.exp() == 0) {
                return true; // return immediately
            }
            return super.onNumberValue(number); // otherwise pump the value to the result JSON
        }
    }
}

只是想澄清一下:您的库旨在在没有中间树表示的情况下即时修改JSON流吗? - terrorrussia-keeps-killing
@fluffy 这个库的目标是以流/事件的方式解析和生成JSON,不需要任何中间或其他对象映射/表示。这就像XML的SAX/StAX。而且该库不应该做比所需更多的工作,并且应该无GC。当然,通过一些转换很容易将事件/令牌的输入序列转换为输出序列,例如进行过滤/跳过或其他无状态(几乎)转换,当您真正不需要将文件完全或甚至部分加载到内存中时。 - AnatolyG
讨论集合的大O问题是很常见的,比如ArrayList vs LinkedList等等。但是当有人试图使用对象映射器或者例如JsonPath从JSON中提取/修改/过滤一个值时,浪费了1000倍以上的CPU时间/内存,这时我的工程师眼睛都要流血了))) - AnatolyG
1
很酷。完全同意关于不太理想的JsonPath或其他类似工具的最后一点。这里最被误解的事情是人们通常不关心这个问题,首先将整个文档加载到字符串中,然后再加载到JSON树中(或更糟糕的是--使用类型绑定,可能会由于读写分别进行的不对称类型绑定而破坏原始文档)。最常用的工具并不适合这种目的,但它们提供了一些易于操作的功能。 - terrorrussia-keeps-killing
我在你回答问题之前建议使用JSON树形表示的原因是,这种表示方式对于初学者来说易于理解和使用。我从未听说过类似于SAX/StAX的JSON库,也曾经在Gson和Jackson上构建了类似的流式处理工具。不确定这些库在仅解析方面有多好,但这比尝试加载大型文档并使服务器实例停止要好得多。所以,是的,你的解决方案真的很酷,并且做得很正确。 - terrorrussia-keeps-killing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接