【问题标题】:How to modify the value of a JsonNode recursively using Jackson如何使用 Jackson 递归地修改 JsonNode 的值
【发布时间】:2019-04-07 14:49:11
【问题描述】:

要求:
我想对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()。我想在每个"value" 的所有"addresses" 内的所有"name" 属性上应用lowercase() 函数。 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 )

【问题讨论】:

  • 输出 dist 仍然是字符串...
  • 更新了代码。
  • 您的问题解决了吗?以下答案有用吗?

标签: java json jackson jsonpath jackson-databind


【解决方案1】:

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

使用方法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;
    }
}

【讨论】:

  • 谢谢@Thomas! :) 非常紧凑和直接的解决方案!
【解决方案2】:

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-s 上工作,由Map&lt;String, Object&gt; 表示。您可以替换给定对象中的键、添加它们、删除它们,就像替换、添加和删除 Map 中的键一样。

杰克逊

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

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,我们的实现可能会更快。

【讨论】:

    猜你喜欢
    • 2023-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-08
    • 2017-01-16
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    相关资源
    最近更新 更多