简体   繁体   English

如何使用 Jackson 递归修改 JsonNode 的值

[英]How to modify the value of a JsonNode recursively using Jackson

Requirements:要求:
I want to apply some functions on the inner values of the JsonNode .我想在JsonNode的内部值上应用一些函数。 The functions can be different eg:- lowercasing some values or appending something to the values or replace the values with something.功能可以不同,例如:- lowercasing某些值或将某些内容附加到值或用某些内容替换值。 How can I achieve that using Jackson library?如何使用Jackson库实现这一目标? Note that the structure of the JSON data can be different which means I want to build a generic system which will accept some path expression which will basically decide where to change.请注意,JSON 数据的结构可能不同,这意味着我想构建一个通用系统,该系统将接受一些路径表达式,这将基本上决定在哪里更改。 I want to use functional programming style, so that I can pass these functions as arguments.我想使用函数式编程风格,以便我可以将这些函数作为参数传递。

eg:例如:

input:输入:

{
  "name": "xyz",
  "values": [
    {
      "id": "xyz1",
      "sal": "1234",
      "addresses": [
        {
          "id": "add1",
          "name": "ABCD",
          "dist": "123"
        },
        {
          "id": "add2",
          "name": "abcd3",
          "dist": "345"
        }
      ]
    },
    {
      "id": "xyz2",
      "sal": "3456",
      "addresses": [
        {
          "id": "add1",
          "name": "abcd",
          "dist": "123"
        },
        {
          "id": "add2",
          "name": "XXXXX",
          "dist": "345"
        }
      ]
    }
  ]
}

In this case I have to two functions basically, lowercase() and convert_to_number() .在这种情况下,我基本上必须使用两个函数, lowercase()convert_to_number() I want to apply lowercase() function on all the "name" attribute inside all the "addresses" of each "value" .我想对每个"value"所有"addresses"内的所有"name"属性应用lowercase()函数。 same goes for convert_to_number() , but for all the "dist" attribute. convert_to_number() ,但对于所有"dist"属性。

So, basically the JSON expressions will be something like below for the functions:因此,对于函数,基本上JSON表达式将类似于以下内容:

lowercase() : /values/*/addresses/*/name
convert_to_number() : /values/*/addresses/*/dist

output:输出:

{
  "name": "xyz",
  "values": [
    {
      "id": "xyz1",
      "sal": "1234",
      "addresses": [
        {
          "id": "add1",
          "name": "abcd",
          "dist": 123
        },
        {
          "id": "add2",
          "name": "abcd3",
          "dist": 345
        }
      ]
    },
    {
      "id": "xyz2",
      "sal": "3456",
      "addresses": [
        {
          "id": "add1",
          "name": "abcd",
          "dist": 123
        },
        {
          "id": "add2",
          "name": "xxxx",
          "dist": 345
        }
      ]
    }
  ]
}

Client code:客户端代码:

JsonNode jsonNode = ...
applyFunctionsRecursivelyBasedOnExpr(JsonNode jsonNode, String expr, Function )

As @MichalZiober in his answer already pointed out, JsonPath offers a much more powerful API than Jackson, when you need to do JSON-path-based operations.正如@MichalZiober 在他的回答中已经指出的那样,当您需要执行基于 JSON 路径的操作时, JsonPath提供了比 Jackson 更强大的 API。

Using methods JsonPath.parse and WriteContext.map you can solve your problem in just a few lines:使用JsonPath.parseWriteContext.map方法,您只需几行就可以解决您的问题:

import java.io.File;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;

public class Main {

    public static void main(String[] args) throws Exception {
        File file = new File("input.json");
        String json = JsonPath.parse(file)
                .map("$.values[*].addresses[*].name", Main::lowerCase)
                .map("$.values[*].addresses[*].dist", Main::convertToNumber)
                .jsonString();
        System.out.println(json);
    }

    private static Object lowerCase(Object currentValue, Configuration configuration) {
        if (currentValue instanceof String)
            return ((String)currentValue).toLowerCase();
        return currentValue;
    }

    private static Object convertToNumber(Object currentValue, Configuration configuration) {
        if (currentValue instanceof String)
            return Integer.valueOf((String)currentValue);
        return currentValue;
    }
}

JsonPath路径

You could use JsonPath library which has a better JSON Path handling.您可以使用具有更好的JSON Path处理的JsonPath库。 When Jackson supports only JSON Pointer draft-ietf-appsawg-json-pointer-03 .Jackson仅支持JSON Pointer draft-ietf-appsawg-json-pointer-03 时 Take a look on JsonPointer documentation.查看JsonPointer文档。 With JsonPath library you could do that in this way:使用JsonPath库,您可以这样做:

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import net.minidev.json.JSONArray;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class JsonPathApp {

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

        JsonModifier jsonModifier = new JsonModifier(jsonFile);
        Function<Map<String, Object>, Void> lowerCaseName = map -> {
            final String key = "name";
            map.put(key, map.get(key).toString().toLowerCase());
            return null;
        };
        Function<Map<String, Object>, Void> changeDistToNumber = map -> {
            final String key = "dist";
            map.put(key, Integer.parseInt(map.get(key).toString()));
            return null;
        };
        jsonModifier.update("$.values[*].addresses[*]", Arrays.asList(lowerCaseName, changeDistToNumber));
        jsonModifier.print();
    }
}

class JsonModifier {

    private final DocumentContext document;

    public JsonModifier(File json) throws IOException {
        this.document = JsonPath.parse(json);
    }

    public void update(String path, List<Function<Map<String, Object>, Void>> transformers) {
        JSONArray array = document.read(path);
        for (int i = 0; i < array.size(); i++) {
            Object o = array.get(i);
            transformers.forEach(t -> {
                t.apply((Map<String, Object>) o);
            });
        }
    }

    public void print() {
        System.out.println(document.jsonString());
    }
}

Your path, should work on JSON object -s which are represented by Map<String, Object> .您的路径应该适用于由Map<String, Object>表示的JSON object -s。 You can replace keys in given object, add them, remove them just like replacing, adding and removing keys in Map .您可以替换给定对象中的键,添加、删除它们,就像在Map替换、添加和删除键一样。

Jackson杰克逊

You can of course mock JsonPath feature by iterating over Json Pointer .您当然可以通过迭代Json Pointer模拟JsonPath功能。 For each * we need to create loop and iterate over it using counter and until node is not missing.对于每个*我们需要创建循环并使用计数器迭代它,直到节点不丢失。 Below you can see simple implementation:下面你可以看到简单的实现:

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

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

public class JsonApp {

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

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

        Function<ObjectNode, Void> lowerCaseName = node -> {
            final String key = "name";
            node.put(key, node.get(key).asText().toLowerCase());
            return null;
        };
        Function<ObjectNode, Void> changeDistToNumber = node -> {
            final String key = "dist";
            node.put(key, Integer.parseInt(node.get(key).asText()));
            return null;
        };

        JsonModifier jsonModifier = new JsonModifier(root);
        jsonModifier.updateAddresses(Arrays.asList(lowerCaseName, changeDistToNumber));

        System.out.println(mapper.writeValueAsString(root));
    }
}

class JsonModifier {

    private final JsonNode root;

    public JsonModifier(JsonNode root) {
        this.root = root;
    }

    public void updateAddresses(List<Function<ObjectNode, Void>> transformers) {
        String path = "/values/%d/addresses/%d";
        for (int v = 0; v < 100; v++) {
            int a = 0;
            do {
                JsonNode address = root.at(String.format(path, v, a++));
                if (address.isMissingNode()) {
                    break;
                }
                if (address.isObject()) {
                    transformers.forEach(t -> t.apply((ObjectNode) address));
                }
            } while (true);
            if (a == 0) {
                break;
            }
        }
    }
}

This solution is slower than with JsonPath because we need to travers whole JSON tree n times where n number of matching nodes.这个解决方案比JsonPath慢,因为我们需要JsonPath整个JSONn次,其中有n个匹配节点。 Of course, our implementation could be a much faster using Streaming API .当然,我们的实现可以使用Streaming API快得多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM