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

问题描述 投票:1回答:2

要求: 我想在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 )
java json jackson jsonpath jackson-databind
2个回答
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中的键一样。

杰克逊

您当然可以通过迭代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慢,因为我们需要遍历整个JSONnn匹配节点的数量。当然,使用Streaming API可以更快地实现。


1
投票

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

使用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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.