Search code examples
jsonansiblejmespath

Filter JSON field from file


I have a JSON file with some data on it, like the below one:

{
    "84d0da32-c945-9844-86cc-4b4bd6100dc5": {
        "UUID": "84d0da32-c945-9844-86cc-4b4bd6100dc5",
        "GroupName": "TEST1",
        "EntryTitle": "host1",
        "Username": "sys",
        "NewPassword": "@PVmkiajauauajjhfz-5/NN"
    },
    "test": {
        "UUID": "5c3c162f-0a80-f949-85a0-afcf9aedb6c8",
        "GroupName": "TEST2",
        "EntryTitle": "host2",
        "Username": "sys",
        "NewPassword": "H7-uPz2mkaaua@ki7q?NSs?"
    }
} 

I am trying to filter only the field GroupName or EntryTitle, with json_query but it always give null, if I try to write all GroupName field, it returns both.
I only want to pass for example if GroupName is TEST2 return the EntryTitle field.

Example of my code until now:

- name: load json data
  shell: cat entries.json
  register: result

- name: save json do var
  set_fact:
    jsondata: "{{ result.stdout | from_json }}"
- name: server name
  set_fact:
    servername: "{{ jsondata | json_query(jq) }}"
  vars:
    jq: "*.GroupName"
- name: Print
  debug:
    msg: "{{ item }}"
  with_items:
    -  "{{ servername }}"

Solution

  • I guess your trial at a query was something like

    *[?GroupName == `TEST2`].EntryTitle
    

    This does indeed returns an empty list ([]) as * is an object projection, and in order to chain a projection on top of another projection, in JMESPath, you have to reset the previous projection, with a pipe expression.

    Here, the query [?GroupName == `TEST2`] is a filter projection, so you indeed have to follow the above mentioned rule by resetting the projection created by the object projection:

    * | [?GroupName == `TEST2`].EntryTitle
    

    So, you should update your server name task accordingly:

    - name: server name
      set_fact:
        servername: "{{ jsondata | json_query(jq) }}"
      vars:
        jq: "* | [?GroupName == `TEST2`].EntryTitle"
    

    Side notes:

    • another good idea would be to use the purposed modules of Ansible rather than a shell task. If the file is on the remote node, use the slurp module, if the file is local to the controller, use a file lookup
    • try to get the habit of using the loop syntax instead of the with_* one, already. There are plenty of examples to migrate them in the documentation and the syntax usually feels more aligned across all different loops of your tasks, as the logic is expressed in filters rather than in the variation of the with_* used

    So, if the file is on the remote node(s):

    - slurp:
        src: entries.json
      register: entries_json
    
    - debug:
        var: >-
          entries_json.content
            | b64decode
            | from_json
            | json_query('* | [?GroupName == `TEST2`].EntryTitle | [0]')
    

    Would yield

    ok: [localhost] => 
      ? |-
        entries_json.content
          | b64decode
          | from_json
          | json_query('* | [?GroupName == `TEST2`].EntryTitle | [0]')
      : host2
    

    And if the file is on the controller node, then you can cut it down to a single task:

    - debug:
        var: >-
          lookup('ansible.builtin.file', 'entries.json')
            | from_json
            | json_query('* | [?GroupName == `TEST2`].EntryTitle | [0]')
    

    Would yield

    ok: [localhost] => 
      ? |-
        lookup('ansible.builtin.file', 'entries.json')
          | from_json
          | json_query('* | [?GroupName == `TEST2`].EntryTitle | [0]')
      : host2
    

    Extra note: the | [0] is once again a pipe expression meant to reset the previous projection, as explained before, in order to get the element [0], so the first one, of the JSON array.