jmespath / jmespath.site

The repo for the jmespath.org website.
http://jmespath.org/
Apache License 2.0
56 stars 51 forks source link

Example on filtering nested non-list objects #20

Open yankov opened 8 years ago

yankov commented 8 years ago

There are a lot of examples on how to filter lists, but that doesn't seem to work on non-list objects. Example:

{"a": 1, "b": {"c" : 100}}

If I want to filter by c == 100 this will not work b[?c==100]. What should be used instead? Also how to filter by a == 1?

dreftymac commented 8 years ago

@yankov

jmespath can do it

Yup, jmespath is outstanding.

The basic idea is to transform any JSON structure into a list of hashes (aka list of objects) (aka list of dictionaries), and then perform the filter queries on that.

[b][?c==`100`].c
[@][?a==`1`].a

Head on over to the jmespath site and experiment with the jmespath in-browser REPL. The jmespath tool allows you to transform anything into a list, which can easily be filtered or queried however you want.

jmespath-awesomeness jmespath-awesomeness

dbarrosop commented 8 years ago

How about the following?

{
   'a': {'v1': 100},
   'b': {'v1': 200},
   'c': {'v1': 100},
}

How can I get all the elements that match v1==100?

dreftymac commented 8 years ago

jmespath can do it

@dbarrosop

Yup, jmespath is outstanding.

First off, we will want to make sure the JSON is well-formed with JSON-compatible syntax.

{
   "a": {"v1": 100},
   "b": {"v1": 200},
   "c": {"v1": 100}
}

Then it is a simple matter of applying the approach specified previously. Transform the JSON structure into a list of objects, and then perform the filter queries on that.

Below is a screenshot of a

solution, which was entered and verified in the JMESPATH in-browser tutorial.

StepXX -- convert the JSON structure into a list of objects

screen shot 2016-10-06 at 10 20 38 am

StepXX -- Query the list of objects on the desired attribute

screen shot 2016-10-06 at 10 34 24 am

Pitfall

BenjaminSchaaf commented 6 years ago

Thanks for the beautiful examples. I've been searching for how I can preserve the initial association keys (the data I need).

For instance, for the following:

{
  "1CR": {
    "disabled": 0,
  },
  "ABY": {
    "disabled": 1,
  },
  "AC": {
    "disabled": 1,
  },
  "ACH": {
    "disabled": 0,
  }
}

I want to get ["1CR", "ACH"], ie. all the ones with disabled == 0.

I can easily use: * | [?disabled==\0`]to get[{"disabled": 0}, {"disabled": 0}]`

Leo-need commented 5 years ago

@dreftymac

Hi ! can you describe way to get initial association of keys ?

dreftymac commented 5 years ago

@Leo-need the examples that I gave above are not all optimal for JmesPath (for example the one that got a thumbs-down).

If you would like, you can paste a "BEFORE and AFTER" example ... show the JSON before, and then what you want to get after applying JmesPath.

For even better results you may want to post a question on StackOverflow.

lazize commented 5 years ago

Hi, sorry if this is not the correct place to ask, but I will try.

From the JSON below, how can I get all obj elements that contais attr with key == "attr 2"?

{
    "top": [
        {
            "obj": [
                {
                    "obj_id": "id 1",
                    "attr": [
                        {
                            "key": "attr 1",
                            "value": "value 1"
                        },
                        {
                            "key": "attr 2",
                            "value": ""
                        }
                    ]
                }
            ]
        },
        {
            "obj": [
                {
                    "obj_id": "id 2",
                    "attr": [
                        {
                            "key": "attr 1",
                            "value": "value 1"
                        }
                    ]
                }
            ]
        }
    ]
}
dwbelliston commented 5 years ago

Maybe gets you closer?

top[?obj[?attr[?key=='attr 2']]][].obj

[
  [
    {
      "obj_id": "id 1",
      "attr": [
        {
          "key": "attr 1",
          "value": "value 1"
        },
        {
          "key": "attr 2",
          "value": ""
        }
      ]
    }
  ]
]
ewithak commented 4 years ago

@dwbelliston , I am trying to do something similar and I found your post useful. Thanks!

jantari commented 4 years ago

Ok so this issue is from 2016 and nobody was able to contribute a working solution to the original question yet.

@BenjaminSchaaf gave a great and concise test case in case any jmespath experts stop by in the future

I'm not one, but I did manage to solve this exact problem in my specific usecase of trying to filter such data in ansible:

- name: Find unformatted disks
  set_fact:
    RAW_DISKS: "{{ ansible_devices | dict2items | json_query(\"[?value.model=='Virtual Disk' && value.vendor=='Msft' && length(value.partitions) == `0`].key\") }}"

this is an ansible task that, in an Azure VM, will find all connected but not formatted disks - like when you just added a new data disk. This is not a pure jmespath solution, it could only be done with the ansible dict2items filter, but at least I finally got this working - in case it helps anybody else who's spent hours googling.

The ansible_devices JSON is huge, so I can only post a cut-down excerpt to better demonstrate how this is exactly like @BenjaminSchaaf example:

"ansible_devices": {
    "loop0": {
        "model": null,
        "partitions": {},
        "vendor": null
        # ... lots more data ...
    },
    "md0": {
        "model": null,
        "partitions": {},
        "vendor": null,
         # ... lots more data ...
    },
    "sda": {
        "model": "Virtual Disk",
        "partitions": {
            "sda1": {
                # ... lots of data about the partition ...
            },
            "sda2": {
                # ... lots of data about the partition ...
            }
        },
        "vendor": "Msft",
         # ... lots more data ...
    },
    "sdb": {
        "model": "Virtual Disk",
        "partitions": {},
        "vendor": "Msft",
    },
    # ... lots more devices ...
}

I need to know the key ('sdb' in this case) so I cannot flatten these objects in the way some people have suggested.

The problem with the ansible-specific approach suggested here: https://stackoverflow.com/a/48038673 is that this will only grab the last disk or other item from the loop, I need ALL matching json objects though (disks in my case).

This isn't a universal answer like I said, but at least it works for ansible.

samsaurabh commented 3 years ago

@jantari Thanks buddy, your solution worked like a charm 💯