I tried three different JSON libraries with support of JsonPath/JsonPointer (Jackson, JsonPath and JSON-P) and none of them is able to reconstruct JSON object hierarchy in case of missing parent nodes. So I came up with my own solution for adding new values to JSON object using Jackson/JsonPointer as it allows to navigate through JsonPointer parts.
private static final ObjectMapper mapper = new ObjectMapper();
public void setJsonPointerValue(ObjectNode node, JsonPointer pointer, JsonNode value) {
JsonPointer parentPointer = pointer.head();
JsonNode parentNode = node.at(parentPointer);
String fieldName = pointer.last().toString().substring(1);
if (parentNode.isMissingNode() || parentNode.isNull()) {
parentNode = StringUtils.isNumeric(fieldName) ? mapper.createArrayNode() : mapper.createObjectNode();
setJsonPointerValue(parentPointer, parentNode); // recursively reconstruct hierarchy
}
if (parentNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) parentNode;
int index = Integer.valueOf(fieldName);
// expand array in case index is greater than array size (like JavaScript does)
for (int i = arrayNode.size(); i <= index; i++) {
arrayNode.addNull();
}
arrayNode.set(index, value);
} else if (parentNode.isObject()) {
((ObjectNode) parentNode).set(fieldName, value);
} else {
throw new IllegalArgumentException("`" + fieldName + "` can't be set for parent node `"
+ parentPointer + "` because parent is not a container but " + parentNode.getNodeType().name());
}
}
Usage:
ObjectNode rootNode = mapper.createObjectNode();
setJsonPointerValue(rootNode, JsonPointer.compile("/root/array/0/name"), new TextNode("John"));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/array/0/age"), new IntNode(17));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/array/4"), new IntNode(12));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/object/num"), new IntNode(81));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/object/str"), new TextNode("text"));
setJsonPointerValue(rootNode, JsonPointer.compile("/descr"), new TextNode("description"));
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode));
This generates and prints the following JSON object:
{
"root" : {
"array" : [ {
"name" : "John",
"age" : 17
}, null, null, null, 12 ],
"object" : {
"num" : 81,
"str" : "text"
}
},
"descr" : "description"
}
For sure, this doesn't cover all corner cases but works in most of the cases. Hope this helps someone else.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…