FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.52k stars 1.38k forks source link

ObjectNode - Provide `put(JsonPointer ptr, JsonNode node)` and `put(JsonPointer ptr, {primitiveDataType} value)` methods #3884

Open SaiKrishna369 opened 1 year ago

SaiKrishna369 commented 1 year ago

There is no mechanism to edit/create a value in the json tree without first navigating to the parent node and then using set or replace or put methods. Starting from version 2.14, within an ObjectNode using withObject and withArray methods, new nodes can be created on a non-existent path. These two methods can further be extended to enhance put method, providing a feature to directly create and set value along a path or modify an existing path.

Describe the solution you'd like put(JsonPointer ptr, JsonNode node) or put(JsonPointer ptr, {primitiveDataType} value) would create the path if it doesn't exist, then auto navigates to the node atptr.head() and sets the provided value at the key ptr.last().

Usage example

@Test
public static void test_nestedArray() {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonObject
        String json = "{}";
        try {
            jsonObject = mapper.readTree(json);
            jsonObject.put("/key1/array1/0/element1", 1);
            jsonObject.put("/key1/array1/0/element2", 2);
            jsonObject.put("/key1/array1/1/element1", 3);
            jsonObject.put("/key1/array1/1/element2", 4);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        assertEquals(jsonObject.toString(), {"key1": { "array1": [ {"element1": 1, "element2": 2}, {"element1": 3, "element2": 4} ] } });
    }

Additional context [Filtering Json] Right now, there is no straight forward way to copy selected paths from one json to another. This feature would simplify this task.

cowtowncoder commented 1 year ago

Couple of quick notes:

  1. JsonNode should only contain read-methods, not mutate, which is why put shouldn't be added there. ObjectNode is where they should go. It is true that withXxx() were added with some mutability but that's probably as far as things should go.
  2. Although there's some existing ambiguity, String should be used sparingly for JsonPointer (since put() already has existing non-pointer semantics so this would be incompatibility, technically)
  3. Number of value types is rather big so it'd be necessary to think of whether to add scalar overloads or not (or just JsonNode value) -- but there's convenience aspect to consider to be sure

So, I think the question is more of ... sort of... renaming of "withObject()" or "withArray()" as "put()". Or perhaps that'd be "putAt()".

So I am not sure this would really work out without really changing definition of JsonNode to be superset of everything any node would do, in a way.

sigpwned commented 1 year ago

I agree with the need here. @cowtowncoder, would you entertain a PR for this feature? I know I'm ignoring cases here, but I'm imagining essentially JsonNode methods like this:

public void with(JsonPointer p, OverwriteMode overwriteMode, boolean preferIndex, JsonNode value) {
    JsonPointer last=p.last();
    if (preferIndex && last.mayMatchElement()) { // array!
        withArray(p.head(), overwriteMode, preferIndex).set(last.getMatchingIndex(), value);
    }
    else {
        withObject(p.head(), overwriteMode, preferIndex).put(last.getMatchingProperty(), value);
    }
}

public void with(JsonPointer p, JsonNode value) {
    with(p, OverwriteMode.ALL, true, value);
}

The use case is that users could read values using a pointer, make some change to the value, and then update the value using a pointer. In my case, I'm storing arrays inside an object in S3 to ensure that my JSON objects fit into the 256KB limit for Amazon Step Functions.

cowtowncoder commented 1 year ago

@sigpwned I guess I'd be willing to remove my objection to addition in JsonNode and so would accept PR for 2.16 for this.

And specifically for something that takes JsonNode as value -- but no primitive/wrapper overloads (too many new method).

I think co-variant return type (return this) would be good too, for chaining.