Search code examples
ansiblejmespathjson-query

json_query filter list of dicts which has value in a list


I have a list of dict mydicts=[{name: foo, data: 1}, {name: foo1, data: 3}, {name: bar, data: 2}] and a list of names names=[foo, foo1, mars, jonh]

I want to create the list of dict only contains names in the list. I know if I want to select single dict I can do jq="[?(name=='foo')]" then mydicts | json_query(jq). However I cannot make the contains version work so far. I need something like [?(contains(names, name))]. Can any one show me an example about how to do this?

It seems json_query correctly get the value name with contains in jq2, but it think it is a variable instead of string. I just found out there is a bug in ansible we need this | to_json | from_json to handle it.

In jq3, it seems it never get the value names I guess I need to wrap both mydicts and names into a dict and pass to jsn_query

- hosts: localhost
  gather_facts: False
  vars:
    mydicts:
      - name: foo
        data: 1
      - name: bar
        data: 2
    names:
      - foo
      - foo1
      - mars
      - jonh
    jq1: "[?(name == 'foo')]"
    jq2: "[?(contains(name, 'f'))]"
    jq3: "[?(contains(names, name))]"
  tasks:
  - debug:
      var: json
  - name: JMEPath test equal
    debug:
      msg: "{{mydicts | json_query(jq1)}}"
  - name: JMEPath test str contain str does not work
    debug:
      msg: "{{mydicts | json_query(jq2)}}"
  - name: JMEPath test another list contain str
    debug:
      msg: "{{mydicts | json_query(jq3)}}"
    ignore_errors: yes
TASK [JMEPath test equal] ************************************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        {
            "data": 1,
            "name": "foo"
        }
    ]
}

TASK [JMEPath test str contain str] **************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "JMESPathError in json_query filter plugin:\nIn function contains(), invalid type for value: foo, expected one of: ['array', 'string'], received: \"unknown\""}
...ignoring

TASK [JMEPath test another list contain str] *****************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "JMESPathError in json_query filter plugin:\nIn function contains(), invalid type for value: None, expected one of: ['array', 'string'], received: \"null\""}
...ignoring

For this simple example, there is a work around: using jinja filter "{{mydicts | selectattr('name', 'in', names) | list}}". But I still need the json_query functionality for deep nested keys.


Solution

  • JMESPath does not have visibility into the jinja2 variables in the same way that the jinja2 filters do, which is why the reference to names doesn't do what you are expecting: JMESPath thinks that is a field on the current-node named names, rather than a reference to that jinja2 variable

    AFAIK you can either construct an artificial input structure just to make both of those pieces of data available to JMESPath (akin to {"names": [...], "mydicts": ...} or you can inline the names list into the JMESPath query:

    - debug:
        msg: '{{ mydicts | json_query(jq) }}'
      vars:
        jq: '[? contains(`{{ names | to_json }}`, name) ]'