Как рекурсивно изменить значение JsonNode с помощью Jackson - PullRequest
1 голос
/ 07 апреля 2019

Требования:
Я хочу применить некоторые функции к внутренним значениям JsonNode.Функции могут быть разными, например: - lowercasing некоторые значения или добавление чего-либо к значениям или замена значений чем-либо.Как мне добиться этого с помощью библиотеки Jackson?Обратите внимание, что структура данных JSON может отличаться, что означает, что я хочу построить общую систему, которая будет принимать некоторое выражение пути, которое в основном решит, где изменить.Я хочу использовать стиль функционального программирования, чтобы передать эти функции в качестве аргументов.

Например:

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

В этом случае мне нужно двафункции в основном, lowercase() и convert_to_number().Я хочу применить функцию lowercase() ко всем атрибутам "name" внутри всех "addresses" каждого "value".То же самое касается convert_to_number(), но для всех атрибутов "dist".

Таким образом, в основном выражения 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
        }
      ]
    }
  ]
}

Код клиента:

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

Ответы [ 2 ]

1 голос
/ 11 апреля 2019

Как уже отметил @MichalZiober в своем ответе, JsonPath предлагает гораздо более мощный API, чем Джексон, когда вам нужно выполнять операции на основе JSON-пути.

Использование методов JsonPath.parse и DocumentContext.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;
    }
}
1 голос
/ 08 апреля 2019

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<String, Object>. Вы можете заменить ключи в данном объекте, добавить их, удалить их так же, как заменять, добавлять и удалять ключи в 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, потому что нам нужно обходить целое JSON дерево n раз, где n количество совпадающих узлов. Конечно, наша реализация могла бы быть намного быстрее, используя Streaming API.

...