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

时间:2019-04-07 14:49:11

标签: java json jackson jsonpath jackson-databind

要求:
我想对JsonNode的内部值应用一些函数。函数可以是不同的,例如:-lowercasing一些值或将某些值附加到值上或将值替换为某些值。如何使用Jackson库来实现?请注意,JSON数据的结构可以不同,这意味着我要构建一个通用系统,该系统将接受一些路径表达式,这些路径表达式将基本上决定要更改的位置。我想使用函数式编程风格,以便可以将这些函数作为参数传递。

例如:

输入:

{
  "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"
        }
      ]
    }
  ]
}

在这种情况下,我基本上必须具有两个功能,lowercase()convert_to_number()。我想在每个lowercase()的所有"name"内的所有"addresses"属性上应用"value"函数。 convert_to_number()也是如此,但所有"dist"属性都是如此。

因此,基本上JSON表达式将类似于以下函数:

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

输出:

{
  "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
        }
      ]
    }
  ]
}

客户代码:

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

2 个答案:

答案 0 :(得分:1)

JsonPath

您可以使用JsonPath库,该库具有更好的JSON Path处理能力。 Jackson仅支持JSON Pointer draft-ietf-appsawg-json-pointer-03时。看一下JsonPointer文档。使用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());
    }
}

您的路径应该适用于JSON object所代表的Map<String, Object> -s。您可以替换给定对象中的键,对其进行添加,删除,就像在Map中进行替换,添加和删除键一样。

Jackson

您当然可以通过迭代JsonPath来模拟Json Pointer功能。对于每个*,我们需要创建循环并使用计数器对其进行迭代,直到不丢失节点。在下面您可以看到简单的实现:

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;
            }
        }
    }
}

此解决方案比JsonPath慢,因为我们需要遍历整个JSONn次,其中n个匹配节点数。当然,使用Streaming API可以更快地实现。

答案 1 :(得分:1)

@MichalZiober在他的回答中已经指出, JsonPath提供了比Jackson更强大的API, 当您需要执行基于JSON路径的操作时。

使用方法JsonPath.parseDocumentContext.map 您只需几行就可以解决您的问题:

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

public class Main {

    public static void main(String[] args) throws Exception {
        File file = new File("input.json");
        DocumentContext context = JsonPath.parse(file);
        context.map("$.values[*].addresses[*].name", Main::lowerCase);
        context.map("$.values[*].addresses[*].dist", Main::convertToNumber);
        String json = context.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;
    }
}