json-path / JsonPath

Java JsonPath implementation
Apache License 2.0
8.92k stars 1.65k forks source link

Filter on children level, return the parent node #287

Open SpikeBlues opened 7 years ago

SpikeBlues commented 7 years ago

I wonder if it is possible to filter on the child level complex objects (not simple string), and retrieve those parent nodes whose children matches the predicts.

Taking the bookstore example from Jayway's github project: https://github.com/jayway/JsonPath#path-examples.

If I would change "author" to a more complex object like following:

  "authors": [
    {
      "firstName": "Nigel",
      "lastName": "Rees"
    },
    {
      "firstName": "Evelyn",
      "lastName": "Waugh"
    }
  ]

With that, how can I get back "all books that were written by someone with last name of 'Waugh' " ?

I have tried with Json Evaluator (http://jsonpath.herokuapp.com/) to use something like $.store.book[?(@.authors[?(@.lastName == 'Waugh')])] , but it seems like the filter doesn't work. I am guessing Jayway does not allow a nested predicates. If that's the case, would there be any workaround for this?

tjakopanec commented 7 years ago

Works fine for me, though your jsonpath has some errors. Change author to authors and just use just the last name in your jsonpath.

This one $.store.book[?(@.authors[?(@.lastName == 'Waugh')])] returns expected result.

SpikeBlues commented 7 years ago

Hi @tjakopanec Thanks for pointing it out. I have fixed my typo. However, I don't see it working as my expectation. The filter does not work as it will return everything including those books whose authors' name were not "Waugh". Would you give it a try?

tjakopanec commented 7 years ago

I've just tried it again and it works as I expect it to run. Just to avoid any confusion, this is my input: { "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "authors": [ { "firstName": "Nigel", "lastName": "Rees" }, { "firstName": "Evelyn", "lastName": "Waugh" } ], "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 }

This is the jsonPath I use: $.store.book[?(@.authors[?(@.lastName == 'Waugh')])]

This is the result I'm getting: [ { "category" : "fiction", "authors" : [ { "firstName" : "Nigel", "lastName" : "Rees" }, { "firstName" : "Evelyn", "lastName" : "Waugh" } ], "title" : "Sword of Honour", "price" : 12.99 } ]

Is this the result you would expect?

SpikeBlues commented 7 years ago

Hi @tjakopanec thanks for the updates. It works with your particular Json input, as the second element in the book array is the only one with a 'complicated' author object. However in my case if all books have a child arraynode authors, the filtering logics does not work as intended. Would you please have a look at my example as follows?

  "store": {
    "book": [
      {
        "category": "reference",
        "authors": [
          {
            "firstName": "Nigel",
            "lastName": "Rees"
          },
          {
            "firstName": "Evelyn",
            "lastName": "Waugh"
          }
        ],
        "title": "Sayings of the Century",
        "price": 8.95
      },
      {
        "category": "fiction",
        "authors": [
          {
            "firstName": "A",
            "lastName": "B"
          },
          {
            "firstName": "C",
            "lastName": "D"
          }
        ],
        "title": "Sword of Honour",
        "price": 12.99
      },
      {
        "category": "fiction",
        "authors": [
          {
            "firstName": "A",
            "lastName": "D"
          },
          {
            "firstName": "Evelyn",
            "lastName": "X"
          }
        ],
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      {
        "category": "fiction",
        "authors": [
          {
            "firstName": "Nigel",
            "lastName": "Rees"
          },
          {
            "firstName": "Evelyn",
            "lastName": "X"
          }
        ],
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  },
  "expensive": 10
}

The JsonPath I am using is the same as yours: $.store.book[?(@.authors[?(@.lastName == 'Waugh')])]

Thank you.

tjakopanec commented 7 years ago

Sorry for the late reply. Yeah, you are right. I've tried it on some of my examples and it's not working correctly.

I guess the author of the library should have a look then.

geewerx commented 7 years ago

+1 Seeing the same issue. This is stopping us us writing any jsonpath queries containing nested filters.

gs-skant commented 7 years ago

Any update on how to fetch parent when filter is applied on child node

tilleti commented 6 years ago

Hi, has there been any update on this issue? If not Jayway's, do any other Jsonpath implementations support this feature (grabbing the parent based on a predicate on a child)?

akshayamaldhure commented 6 years ago

Really looking forward to have this issue resolved for the Jayway implementation.

guranya commented 6 years ago

This functionality is needed in our project. Any updates?

pavithrakamath commented 6 years ago

really needed feature to extract parent data from filtering nested arrays

BrandonDudek commented 6 years ago

+1

pavelgordon commented 6 years ago

+1

oprince commented 6 years ago

+1

ravimandli commented 6 years ago

+1

lamp12 commented 6 years ago

+1

igetsman commented 6 years ago

+1

rafaelaazevedo commented 6 years ago

+1

rafaelaazevedo commented 6 years ago

just found a workaround, now I am using the library "jsonpath-plus": "^0.16.0" and I am able to search and go back to the parent node using the expression: $..[?(@.firstName=="A")]^title

tristanbrandt014 commented 6 years ago

+1

geewerx commented 6 years ago

Project appears to be dormant...

paragguruji commented 6 years ago

+1

Ordiel commented 6 years ago

+1

vtkstef commented 5 years ago

+1

happynev commented 5 years ago

+1

MaksSieve commented 5 years ago

+1

sanjana-bhat commented 5 years ago

This works for me if authors was a map and not a list $.store.book[?('Waugh' == @['authors']['lastName'])]. This works as well $.store.book[?('Waugh' == @['authors'][1]['lastName'])] but it doesn't seem to like using wildcard in expressions

ersione commented 5 years ago

$.store[?(@.friends[?(@.name == 'Shawn Holman')])] I hope to it will works fine.

ghost commented 4 years ago

Is there any any update on this one, this would be really helpful.

codermrrob commented 4 years ago

I found this works as expected with Json.Net, at least with version 12.0.3

My query $.purchases[?(@.payments[?(@.type == 'CASH')])]

I can't post the data here without a lot of heavy editing but the query is the same nested array query to return the parent purchase objects where there is a payment with type CASH.

andrew-property-xyz commented 4 years ago

Having same issue but perhaps this behaviour is as per jsonpath spec. If authors is an array, the parent node of a filter for, say, lastname=="Waugh" is simply the authors array (and not the book object to which the array belongs).

The jsonpath-plus module behaves the same. So, we either need support for nested filters or an option to get the parent of the parent (i.e. the parent of the array which will be the book object I'm looking for), or to ignore/skip arrays when getting the parent.

Currently, to get the desired result, I have to first do a paths request and then a value request using the object index extracted from the paths result. So, two jsonpath evaluations to find a book (or books) that have an author with a lastname of "Waugh".

serhatdirihan commented 3 years ago

As a workaround (it works on https://jsonpath.herokuapp.com/ but not other simulators):

JsonPath : $.[?(@.c[*].d contains "d3" )]['a', 'b']

Input : [ { "a": "a1", "b": "b1", "c": [ { "d": "d1", "e": "e1" }, { "d": "d2", "e": "e2" } ] }, { "a": "a2", "b": "b2", "c": [ { "d": "d3", "e": "e3" } ] } ]

It brings the correct result :

[ { "a" : "a2", "b" : "b2" } ]

kean-logan commented 3 years ago

Building off of the previous answer from ArtVandalayy - using the modified book store example input (with authors as a complex object):

{
    "store": {
        "book": [
            {
                "category": "reference",
                "authors": [
                    {
                        "firstName": "Nigel",
                        "lastName": "Rees"
                    },
                    {
                        "firstName": "Evelyn",
                        "lastName": "Waugh"
                    }
                ],
                "title": "Sayings of the Century",
                "price": 8.95
            },
            {
                "category": "fiction",
                "authors": [
                    {
                        "firstName": "Herman",
                        "lastName": "Melville"
                    },
                    {
                        "firstName": "Somebody",
                        "lastName": "Else"
                    }
                ],
                "title": "Moby Dick",
                "isbn": "0-553-21311-3",
                "price": 8.99
            },
            {
                "category": "fiction",
                "authors": [
                    {
                        "firstName": "Evelyn",
                        "lastName": "Waugh"
                    },
                    {
                        "firstName": "Nigel",
                        "lastName": "Rees"
                    }
                ],
                "title": "Sword of Honour",
                "price": 12.99
            },
            {
                "category": "fiction",
                "authors": [
                    {
                        "firstName": "J. R. R.",
                        "lastName": "Tolkien"
                    }
                ],
                "title": "The Lord of the Rings",
                "isbn": "0-395-19395-8",
                "price": 22.99
            }
        ],
        "bicycle": {
            "color": "red",
            "price": 19.95
        }
    },
    "expensive": 10
}

Using this path, which filters on authors with last name given: $.store.book[?(@.authors..lastName contains "Waugh")].title

Or alternate path if deepscan operator '..' is not supported: $.store.book[?(@.authors[*].lastName contains "Waugh")].title

We get this correct output according to https://jsonpath.herokuapp.com/ ver 2.3.0 - 2017-07-04 14:54:00

[
   "Sayings of the Century",
   "Sword of Honour"
]
rasheedzrt commented 3 years ago

Hello Everyone,

I have a similar requirement.

{ "store" : { "10162021" : { "id" : 812340, "properties" : { "server" : "server1.example.org", "serverip" : "", } }, "10162022" : { "properties" : { "serverip" : "127.0.0.1", "server" : "server2.example.org", }, "id" : 8890890 } } }

where I need the value of the entries right under sore like these values "10162022" "10162021" from above code.

appreciate any suggestions.

tolpp commented 3 years ago

It's may not be a bug. @ArtVandalayy makes a good point. As stated in documentation, filter expression must evaluate to a boolean value.

The problem with previous solution $.store.book[?(@.authors[?(@.lastName == 'Waugh')])] is;

@ArtVandalayy's solution converts main expression to the boolean;

$.[?(@.c[*].d contains "d3" )]['a', 'b']

If we convert it for the example, it will be something like

$.store.book[?(@.authors[?(@.lastName == 'Waugh')].lastName contains 'Waugh')]

Yeah, it works, but there are other options.

Better solution

We can check for an empty.

$.store.book[?(@.authors[?(@.lastName == 'Waugh')] empty false)]

When filter expression checks for an empty false, then result contains only the books with an author that last name is 'Waugh'. When empty true, the result will become the inverted version of the first one. The books of authors with lastName 'Waugh' will become excluded from the list.

And, there are more filter operators to use: https://github.com/json-path/JsonPath#filter-operators

raviraj-bhalerao commented 1 year ago

@tolpp, @dengmingcong, @thadguidry, @VerkhovtsovPavel

I tried $.store.book[?(@.authors[?(@.lastName == 'Waugh')] empty false)] and $.store.book[?(@.authors[?(@.lastName == 'Waugh')].lastName contains 'Waugh')] with jsonPath.Com and newtonsoft 13.0.3.

It does not work.

JsonPath fails as - "jsonPath: Unexpected token '?': _$v.authors[?($_v.lastName == 'Waugh'"

NewtonSoft fails as - "'Could not read query operator.'"

Am I missing anything?

tolpp commented 1 year ago

I tried $.store.book[?(@.authors[?(@.lastName == 'Waugh')] empty false)] and $.store.book[?(@.authors[?(@.lastName == 'Waugh')].lastName contains 'Waugh')] with jsonPath.Com and newtonsoft 13.0.3.

It does not work.

JsonPath fails as - "jsonPath: Unexpected token '?': _$v.authors[?($_v.lastName == 'Waugh'"

NewtonSoft fails as - "'Could not read query operator.'"

Am I missing anything?

I think jsonpath[.]com is based on different JsonPath implementation.(https://github.com/ashphy/jsonpath-online-evaluator)

It still works on https[:/]/www.javainuse[.]com/jsonpath

@raviraj-bhalerao

raviraj-bhalerao commented 1 year ago

@tolpp That was very quick ans. And thank you very much. True it works with javainuse.

Would you have any idea where I can see how nested queries can be written for newtonsoft or how this query should be changed to get same results in newtonsoft please?