Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creation of deep nodes #83

Open
wabrit opened this issue Apr 28, 2015 · 14 comments
Open

Creation of deep nodes #83

wabrit opened this issue Apr 28, 2015 · 14 comments
Labels

Comments

@wabrit
Copy link

wabrit commented Apr 28, 2015

It would be useful if JsonPath had the capability to create "deep nodes" rather than just nodes of already existing parents.

For example, provide a method that, given a path e.g. $.x.y.z, a value 10, and a DocumentContext, create the node z with a value 10 allowing for the possibility that x, y do not yet exist. The logic would be

  • If the 1st level node x does not yet exist, create it with an empty object value
  • If the 2nd level node x.y does not yet exist, create it with an empty object value (i.e. create a new property y of x)
  • If the 3rd level node x.y.z does not yet exist, create it with value 10; if it does exist update the value to 10.

It's possible to do this with the existing APIs using an iterative loop to work down the path, DocumentContext.read to test for existence and DocumentContext.put to create, but would be really cool if the API provided such a capability generally.

@drekbour
Copy link

Also $.array[10] could create an array with anough size to assign the 10th item

@jlawniczak340
Copy link

Is there any more thought to bringing this functionality to JsonPath?
It would really make this tool much more flexible in terms of full json data manipulation.

@jlolling
Copy link
Contributor

Yes this would be nice to have in the API. I have done this for my own. I have wrapped the API and in my class there is a method getNode(node, jsonpath) which creates all missing nodes. But I have done the easy way and support only the dot-notation and not the bracket-notation.

@jlawniczak340
Copy link

jlawniczak340 commented Jun 17, 2016

@jlolling I took a look through your forked repo and couldn't find the class you were referring to. Did you push that class up to a public repo somewhere? I am doing something similar and was hoping for inspiration on how to handle arrays.

@jlolling
Copy link
Contributor

I made it in my own application. I should get an idea where to put that in the existing Jayways API.
The code where I made this feature is here:
https://github.com/jlolling/talendcomp_tJSONDoc
in the class JsonDocument.getNode(...)
public JsonNode getNode(JsonNode parentNode, String jsonPath, boolean create)

If you have an idea where we can integrate this in the Jayway API - that would be great.

@jlawniczak340
Copy link

I see that now, look like you are using the Jackson providers. I started down the path of using the default jsonsmart providers instead. I am wrapping the set, add, and put methods. Possibly wrap them all together since I am looking for a generic way to add anything to the document, without explicitly knowing what the path is. Also only supporting dot notation.

@jlolling
Copy link
Contributor

I guess it should be possible to build such functionality for all json providers. But for my project goal I needed that very quick - so I have done this only for my own.

@drekbour
Copy link

Functional workaround for limited syntax with limited performance. I do hope this library comes back to life or something yaml-based supercedes it.

  /**
   * Sets a value, creating any missing parents
   * @param context
   * @param path supports only "definite" paths in the simple format {@code $.a.b.c}.
   * @param value value to set
   */
  private void create(DocumentContext context, String path, Object value) {
    int pos = path.lastIndexOf('.');
    String parent = path.substring(0, pos);
    String child = path.substring(pos + 1);
    try {
      context.read(parent); // EX if parent missing
    } catch (PathNotFoundException e) {
      create(context, parent, new LinkedHashMap<>()); // (recursively) Create missing parent
    }
    context.put(parent, child, value);
  }

@kumble-004
Copy link

Any update on this feature?

@AlfredAndroidTedmob
Copy link

Functional workaround for limited syntax with limited performance. I do hope this library comes back to life or something yaml-based supercedes it.

  /**
   * Sets a value, creating any missing parents
   * @param context
   * @param path supports only "definite" paths in the simple format {@code $.a.b.c}.
   * @param value value to set
   */
  private void create(DocumentContext context, String path, Object value) {
    int pos = path.lastIndexOf('.');
    String parent = path.substring(0, pos);
    String child = path.substring(pos + 1);
    try {
      context.read(parent); // EX if parent missing
    } catch (PathNotFoundException e) {
      create(context, parent, new LinkedHashMap<>()); // (recursively) Create missing parent
    }
    context.put(parent, child, value);
  }

Thanks for the workaround.

I just found out about JsonPath, and I wish I knew about it before.

We had before a requirement from a client to convert a list of full path key-value pairs to a JSON object (the scenario is more complicated related to a dynamic input form, but this is the end result for brevity and to stay on point),
e.g:

listOf(
    "valueDetail.valueDetail1" to "1",
    "valueDetail.valueDetail2" to 2,
    "valueDetail.valueDetail3.someKey1" to "test",
    "valueDetail.valueDetail3.someKey2" to 4.5,
)

to

{
  "valueDetail": {
    "valueDetail1": "1",
    "valueDetail2": 2,
    "valueDetail3": {
      "someKey1": "test",
      "someKey2": 4.5
    }
  }
}

We also had to take into consideration arrays for that case.

@juja0
Copy link

juja0 commented Sep 28, 2021

Another potential (limited, map-only, no arrays) workaround is to use a custom JsonProvider which returns an empty map from the method getMapValue when the resolved value is UNDEFINED.

Something similar to this.

public class PathGeneratingJsonProvider extends JsonSmartJsonProvider
{
    @Override
    public Object getMapValue(Object obj, String key)
    {
        Object value = super.getMapValue(obj, key);
        return value == JsonProvider.UNDEFINED ? new LinkedHashMap<>() : value;
    }
}

Unsure how this will behave when used for reading json so make sure this is used only for update operations and use the default jsonProvider for read operations.

@els-krasils
Copy link

Functional workaround for limited syntax with limited performance. I do hope this library comes back to life or something yaml-based supercedes it.

  /**
   * Sets a value, creating any missing parents
   * @param context
   * @param path supports only "definite" paths in the simple format {@code $.a.b.c}.
   * @param value value to set
   */
  private void create(DocumentContext context, String path, Object value) {
    int pos = path.lastIndexOf('.');
    String parent = path.substring(0, pos);
    String child = path.substring(pos + 1);
    try {
      context.read(parent); // EX if parent missing
    } catch (PathNotFoundException e) {
      create(context, parent, new LinkedHashMap<>()); // (recursively) Create missing parent
    }
    context.put(parent, child, value);
  }

A little lame extension for one by one array elements adding:

public static void set(DocumentContext context, String path, Object value) {
    String parentPath;
    String key;
    Integer index;
    // parse the path ending
    if (path.endsWith("]")) {
        int pos = path.lastIndexOf('[');
        parentPath = path.substring(0, pos);
        key = path.substring(pos + 1, path.length() - 1);
        try {
            index = Integer.parseInt(key);
        } catch (NumberFormatException nfe) {
            throw new RuntimeException("Unsupported value \"" + key + "\" for index, only non-negative integers are expected; path: \"" + path + "\"");
        }
    } else {
        int pos = path.lastIndexOf('.');
        parentPath = path.substring(0, pos);
        key = path.substring(pos + 1);
        index = null;
    }
    // ensure parent exists
    try {
        context.read(parentPath);
    } catch (PathNotFoundException e) {
        if (index != null) {
            set(context, parentPath, new ArrayList<String>());
        } else {
            set(context, parentPath, new LinkedHashMap<>());
        }
    }
    // set the value
    if (index != null) {
        List<Object> parent = context.read(parentPath);
        if (index == parent.size()) {
            context.add(parentPath, value);
        } else if (index < parent.size()) {
            context.set(path, value);
        } else {
            throw new RuntimeException("Array values should be added one by one, but trying to put value at index " + index + " while array size is " + parent.size() + " (path: \"" + parentPath + "\")");
        }
    } else {
        context.put(parentPath, key, value);
    }
}

@Jayesslee
Copy link

Any update on this feature?

@blacelle
Copy link

blacelle commented May 2, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests