How to add new node to Json using JsonPath?

11,599

Solution 1

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.

Solution 2

To create a new node try put(path, key, object) on the WriteContext interface implemented by the result of JsonPath.parse(jsonString).

Solution 3

You can do it as follows:

JsonPath.parse(jsonString).set(JsonPath.compile("$.user.name"), "John");
Share:
11,599

Related videos on Youtube

Uni
Author by

Uni

Updated on June 19, 2022

Comments

  • Uni
    Uni almost 2 years

    I'm working with JSON and facing some problems.

    I want to insert/update a path in a JSON object. In the case that the path doesn't exist, it will be created then I insert a new value. In case that it exits, it will be updated by a new value

    For example, I want to add new path like this:

    val doc = JsonPath.parse(jsonString)
    doc.add("$.user.name", "John")
    

    but I always get this error, because the path doesn't exist:

    class com.jayway.jsonpath.PathNotFoundException : Missing property in path $['user']

    Therefore I want to create a new path if it does not exist.

    This is my code, but jsonString doesn't change:

    var jsonString = "{}" val conf = Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL).addOptions(Option.SUPPRESS_EXCEPTIONS)
    JsonPath.using(conf).parse(jsonString).set(JsonPath.compile("$.user.name"), "John") 
    Log.d("TAG", "new json = $jsonString") 
    

    Please give me your advice. Thank you very much!!

  • Uni
    Uni almost 6 years
    I'm supposing that the beginning json is empty like this {} I still get this error class com.jayway.jsonpath.PathNotFoundException : Missing property in path $['user']
  • NumeroUno
    NumeroUno almost 6 years
    Can you try with following: private static Configuration conf = Configuration.defaultConfiguration().addOptions(Option.DEFAU‌​LT_PATH_LEAF_TO_NULL‌​).addOptions(Option.‌​SUPPRESS_EXCEPTIONS)‌​; JsonPath.using(conf).parse(...
  • Uni
    Uni almost 6 years
    var jsonString = "{}" val conf = Configuration.defaultConfiguration().addOptions(Option.DEFAU‌​LT_PATH_LEAF_TO_NULL‌​).addOptions(Option.‌​SUPPRESS_EXCEPTIONS) JsonPath.using(conf).parse(jsonString).set(JsonPath.compile(‌​"$.user.name"), "John") Log.d("TAG", "new json = $jsonString") I tried your solution but jsonString doesn't change. Could you please help? I'm using Kotlin language. Thank you
  • Uni
    Uni almost 6 years
    It seems that it didn't work. Could you plz help? Thank you
  • Leon Proskurov
    Leon Proskurov almost 4 years
    This looks as a good solution when includes the Configuration.defaultConfiguration().addOptions(Option.DEFAU‌​LT_PATH_LEAF_TO_NULL‌​).addOptions(Option.‌​SUPPRESS_EXCEPTIONS)‌​, but what if i want to add nodes to an array?
  • aksh1618
    aksh1618 over 3 years
    @NumeroUno Even with configuration, this works for a single key, but not for nested path; i.e. <"{}", "$.key", "value"> works, but <"{}", "$.nested.key", "value"> doesn't.
  • Yu Jiaao
    Yu Jiaao almost 3 years
    setJsonPointerValue(node, parentPointer, parentNode); // recursively reconstruct hierarchy