JsonPath: Filter expression return array can not use [index] get item inside
eg. I want get the price for book “Sayings of the Century”
👍 $.store.book[?(@.title=='Sayings of the Century')]
will return an book array
👍 $.store.book[?(@.title=='Sayings of the Century')].price
will return an price array
😂 $.store.book[?(@.title=='Sayings of the Century')][0]
will return an empty array
😂 $.store.book[?(@.title=='Sayings of the Century')].price[0]
will return an empty array
I think $.store.book[?(@.title=='Sayings of the Century')][0]
should return a book
$.store.book[?(@.title=='Sayings of the Century')].price[0]
should return a price
About this issue
- Original URL
- State: open
- Created 8 years ago
- Reactions: 92
- Comments: 35 (5 by maintainers)
I know that this is not the same as being able to select any given element, but I think a lot of people end up here because they are looking for a way to select the first (or maybe last) element of the resulting array from an applied filter. Therefore I don’t think we should necessarily go for:
And expect a specific element of the array, because square brackets, for JSON path, is either square bracket notation or applying a filter (and there’s nothing in the spec that specifically covers this use case where we want to mix both).
I would argue the closest way to adhere to the spec is to add some more methods, called after the filter, the same way we do for
.min()
and.max()
- except they are not tied to the values of the array, but instead the elements, namely:.first()
and.last()
.It’s not as powerful but could be easier to implement and resolve a number of people’s issues?
Almost 5 years people are struggling with it and unfortunately no any progress here 😦 Too sad 😦
My 2 cents. This is a workaround using read method on the result of the filter:
String filterResult = JsonPath.read(fullJson, "$.store.book[?(@.title == 'Sayings of the Century')]").toJSONString();
Double price = JsonPath.read(filterResult, "$[0].price");
Hope it could help until we will be able to do sort of:
$.store.book[?(@.title=='Sayings of the Century')][0].price
A path must point to something in the document. That is the case for:
$.store.book[?(@.title=='Sayings of the Century')]
but not with:
$.store.book[?(@.title=='Sayings of the Century')][0]
where the [0] actually is expected to be applied to the result of the path evaluation. I agree that this would useful in many situations but it should not be confused with the actual path.
Any update on this issue?
I’m having problems understanding how come a filtered array is not an array, that is without any knowledge of library internals.
@jochenberger and @kallestenflo is there any support on this. Filtering should not lead to a non accessible array.
The problem is getting old and a solution is not found yet in which this is handled within the JSONPATH call itself without additional script functions
I’ve just come across this issue as well. I’d assumed that JSONPath was the JSON version of XPath for XML documents.
The not being able to index the result of a filter is quite a pain. This ticket has been open for 7 years now. Any prospect of it happening?
@fhoeben @bhreinb Sorry for late response. My parser is already published at https://github.com/zakjan/objectpath . It supports more advanced cases, might be too complex for general use cases. Feel free to use it as a reference for building your own parser.
@zakjan any chance you published that parser? Maybe others could benefit also.
I moved to
With this dependency I’m able to use a json path like
$.energies[?(@.type == 'Electric')].level[0]
which return the first element as needed here a sample usage:@fhoeben Yeah, I’ll try to extract it and share
Good to see I am not the only one struggling with this. I would expect such a filter to return whatever the content type is, not forced in a single result array.
My workaround is to parse the one result to a string and either add the following to the assert somewhere:
expectedResult = "[\"" + expectedResult + "\"]"
or strip the last and first two characters from the String returned but somehow I feel that is worse.Is it because you stay with the content type list and filter inside that? In which case I would have to say I see why you went with a single entry list and I will alter my approach to use something like what I found at @fhoeben 's commit and use
result.get(0)
Yeah, it all makes a lot more sense now.
Still would like it to return the object type of the actual object referenced to.
Struggeling with the same and @kallestenflo have a hard time to understand your argument:
The result of the path operation after the filter is a JSONArray, so at that point he document is a JSON Array, e.g. with 1 element. So now this is a new document and [0] points into the first element of this new intermediate document.
Let’s study further based on my current real world example, parsing cloud foundry environment information.
Realworld example, a typical vcap_service cloudfoundry env variable value: { “mysql56”: [ { “credentials”: { “dbname”: “asfawrwer”, “hostname”: “10.11.12.133”, “password”: “awerawerwerweaar”, “port”: “44444”, “ports”: { “3306/tcp”: “55555” }, “uri”: “mysql://awrwefawefaewr:awerawerwerweaar@10.11.12.133:55555/asfawrwer”, “username”: “awrwefawefaewr” }, “label”: “mysql56”, “name”: “my-persistence”, “plan”: “free”, “tags”: [ “mysql56” ] } ] }
And we need to access the credentials, e.g. the hostname: $.*[?(@.name == ‘my-persistence’)].credentials.hostname
Currently this returns a JSONArray of 1 element so I tried: $.*[?(@.name == ‘my-persistence’)][0].credentials.hostname
and
$.*[?(@.name == ‘my-persistence’)].credentials.hostname[0]
to get a clean String value returned, but no luck due to this issue.
In my view after: $.*[?(@.name == ‘my-persistence’)]
The ‘intermediate document’ that the next operator is applied to is: [ { “credentials”: { “dbname”: “asfawrwer”, “hostname”: “10.11.12.133”, “password”: “awerawerwerweaar”, “port”: “44444”, “ports”: { “3306/tcp”: “55555” }, “uri”: “mysql://awrwefawefaewr:awerawerwerweaar@10.11.12.133:55555/asfawrwer”, “username”: “awrwefawefaewr” }, “label”: “mysql56”, “name”: “my-persistence”, “plan”: “free”, “tags”: [ “mysql56” ] } ]
(because it is valid to access $.*[?(@.name == ‘my-persistence’)].credentials.hostname which returns [ “10.11.12.133” ] )
So why not allow the path to navigate to [0], after which the intermediate document is: { “credentials”: { “dbname”: “asfawrwer”, “hostname”: “10.11.12.133”, “password”: “awerawerwerweaar”, “port”: “44444”, “ports”: { “3306/tcp”: “55555” }, “uri”: “mysql://awrwefawefaewr:awerawerwerweaar@10.11.12.133:55555/asfawrwer”, “username”: “awrwefawefaewr” }, “label”: “mysql56”, “name”: “my-persistence”, “plan”: “free”, “tags”: [ “mysql56” ] }
then naviate to credentials.hostname and get a clean string value “10.11.12.133” ?
I found out a possible solution:
$.store.book[?(@.title=='Sayings of the Century')].price.min()
would take the one value from the list…