要求:
我想对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 )
答案 0 :(得分:1)
您可以使用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
慢,因为我们需要遍历整个JSON
树n
次,其中n
个匹配节点数。当然,使用Streaming API
可以更快地实现。
答案 1 :(得分:1)
@MichalZiober在他的回答中已经指出, JsonPath提供了比Jackson更强大的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;
}
}