Search code examples
ansiblejinja2jmespath

Retain null in nested JMESPath query with Ansible


I make an API call in ansible and analyse the response. A repro:

---
- hosts: localhost
  gather_facts: false

  vars:
    response: {
        "results": [
          {
            "item": "server1",
            "json": {
              "data": null,         # <----- here's the problem
              "ok": true
            },
            "status": 200
          },
          {
            "item": "server2",
            "json": {
              "data": [
                {
                  "id": 7,
                  "name": "Production5"
                }
              ],
              "ok": true
            },
            "status": 200
          }
        ]
      }

  tasks:
    - debug:
        msg: "{{ response.results | community.general.json_query(_query) }}"
      vars:
        _query: "[].json.data[*].id | []"

Which yields:

TASK [debug] *********************************************
ok: [localhost] => {
    "msg": [
        7
    ]
}

But I actually need:

TASK [debug] *********************************************
ok: [localhost] => {
    "msg": [
        null,                    # <-----
        7
    ]
}

I want to keep the null so the input and output arrays have the same length, as this is important in future tasks.

I tried converting the null without success, e.g.: ([].json.data || [])[*].id and not_null([].json.data, '[]')[*].id.


Solution

  • The filter json_query is not capable of doing this, I'm afraid. Try Jinja instead

      result: |
        {% filter from_yaml %}
        {% for i in response.results|map(attribute='json.data') %}
        - {{ i.0.id|default('null') }}
        {% endfor %}
        {% endfilter %}
    

    gives what you want

      result:
        - null
        - 7
    

    Optionally, use json_query, create the lists of values, and map the value. The below expression gives the same result

      result: "{{ response.results|
                  json_query('[].[json.data[0].id, item]')|
                  map('first') }}"
    

    • Example of a complete playbook for testing
    - hosts: localhost
    
      vars:
    
        response:
          results:
            - item: server1
              json:
                data: null
                ok: true
              status: 200
            - item: server2
              json:
                data:
                  - {id: 7, name: Production5}
                ok: true
              status: 200
    
        result1: |
          {% filter from_yaml %}
          {% for i in response.results|map(attribute='json.data') %}
          - {{ i.0.id|default('null') }}
          {% endfor %}
          {% endfilter %}
    
        result2: "{{ response.results|
                     json_query('[].[json.data[0].id, item]')|
                     map('first') }}"
    
      tasks:
    
        - debug:
            var: result1
        - debug:
            var: result2