-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Add painless method getByPath, get value from nested collections with dotted path #43170
Add painless method getByPath, get value from nested collections with dotted path #43170
Conversation
… dotted path Given a nested structure composed of Lists and Maps, getByPath will return the value keyed by path. getByPath is a method on Lists and Maps. The path is string Map keys and integer List indices separated by dot. An optional third argument returns a default value if the path lookup fails due to a missing value. Eg. ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1') = ['c', 'd'] ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1.0') = 'c' ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key2', 'x') = 'x' [['key0': 'value0'], ['key1': 'value1']].getByPath('1.key1') = 'value1' Throws IllegalArgumentException if an item cannot be found and a default is not given. Throws NumberFormatException if a path element operating on a List is not an integer. Fixes elastic#42769
Pinging @elastic/es-core-infra |
…ss/42769-getByPath
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few comments. Overall I think we can make this cleaner by using recursion in this case, since we need to switch back and forth between types. You could still do this iteratively, but then I would have the first part of the loop do the extraction of the next element, and the second part do the dispatch of the next receiver. The downside is we would still have an extra instanceof check when we already know the receiver type from the whitelisted methods.
I also am wondering if we should be more strict in the default value behavior, but still allow for missing behavior. My suggestion would be to make this work strictly using the current dot syntax, and in a followup add support in the path syntax for the null-safe operator. This way a user can be very explicit if they want lenient behavior at any element within the path.
modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java
Outdated
Show resolved
Hide resolved
modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java
Show resolved
Hide resolved
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java
Outdated
Show resolved
Hide resolved
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java
Outdated
Show resolved
Hide resolved
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java
Outdated
Show resolved
Hide resolved
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java
Outdated
Show resolved
Hide resolved
Separate getByPath tests into different test methods rather than a single test driven by cases. Raise IllegalArgumentException when object at non-terminal path element is not a container, even if defaultValue is given. Check for double . in paths and paths ending in .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, just a few more minor comments.
modules/lang-painless/src/test/java/org/elasticsearch/painless/GetByPathAugmentationTests.java
Outdated
Show resolved
Hide resolved
modules/lang-painless/src/test/java/org/elasticsearch/painless/GetByPathAugmentationTests.java
Outdated
Show resolved
Hide resolved
* Throws 'IllegalArgumentException' if elements[i] is an empty string, indicating double separators | ||
* or path ends in a separator. | ||
*/ | ||
private static Object getByPath(Object obj, String[] elements, int i, Supplier<Object> defaultSupplier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be more clear with each of these names having one more word. Eg Dispatch here, Map/List below.
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java
Outdated
Show resolved
Hide resolved
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java
Outdated
Show resolved
Hide resolved
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java
Outdated
Show resolved
Hide resolved
Rename helper functions. Use javadoc for public functions. Optimize empty path check.
…ss/42769-getByPath
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
modules/lang-painless/src/test/java/org/elasticsearch/painless/GetByPathAugmentationTests.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks for adding this method Stuart!
…ss/42769-getByPath
… dotted path (elastic#43170) Given a nested structure composed of Lists and Maps, getByPath will return the value keyed by path. getByPath is a method on Lists and Maps. The path is string Map keys and integer List indices separated by dot. An optional third argument returns a default value if the path lookup fails due to a missing value. Eg. ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1') = ['c', 'd'] ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1.0') = 'c' ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key2', 'x') = 'x' [['key0': 'value0'], ['key1': 'value1']].getByPath('1.key1') = 'value1' Throws IllegalArgumentException if an item cannot be found and a default is not given. Throws NumberFormatException if a path element operating on a List is not an integer. Fixes elastic#42769
… dotted path (#43170) (#43606) Given a nested structure composed of Lists and Maps, getByPath will return the value keyed by path. getByPath is a method on Lists and Maps. The path is string Map keys and integer List indices separated by dot. An optional third argument returns a default value if the path lookup fails due to a missing value. Eg. ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1') = ['c', 'd'] ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1.0') = 'c' ['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key2', 'x') = 'x' [['key0': 'value0'], ['key1': 'value1']].getByPath('1.key1') = 'value1' Throws IllegalArgumentException if an item cannot be found and a default is not given. Throws NumberFormatException if a path element operating on a List is not an integer. Fixes #42769
Given a nested structure composed of Lists and Maps, getByPath will return the value
keyed by path. getByPath is a method on Lists and Maps.
The path is string Map keys and integer List indices separated by dot. An optional third
argument returns a default value if the path lookup fails due to a missing value.
Eg.
['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1') = ['c', 'd']
['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key1.0') = 'c'
['key0': ['a', 'b'], 'key1': ['c', 'd']].getByPath('key2', 'x') = 'x'
[['key0': 'value0'], ['key1': 'value1']].getByPath('1.key1') = 'value1'
Throws IllegalArgumentException if an item cannot be found and a default is not given.
Throws NumberFormatException if a path element operating on a List is not an integer.
Fixes #42769