Search code examples
ansiblejinja2

How can get an attribute after map()?


I have some JSON output I need to loop through. It's basically a list within a dictionary within a list, etc. Here's an example:

{
  "results": [
    {
      "children": {
        "attachment": {
          "results": [
            {
              "history": {},
              "title": "SomeTitle"
            }
          ]
        }
      }
    }
  ]
}

I need to iterate through the second results list (with the history and title, etc attributes) but I don't know how to get past the children attribute. Here's the filter I've tried:

results | map(attribute='children').attachment.results

It throws an error that the .attachment.results doesn't belong after map(). So what's the right way to accomplish this?


Solution

  • Use the lookup plugin subelements. How exactly you can use it depends on what attributes can change.

    • For example, given the list (to demonstrate the iteration let's add one more item to children.attachment.results)
      results1:
        - children:
            attachment:
              results:
                - history: {}
                  title: SomeTitle
                - future: {}
                  title: OtherTitle
    

    the task

        - debug:
            msg: "{{ item }}"
          loop: "{{ results1|subelements('children.attachment.results') }}"
          loop_control:
            label: "{{ item.0.keys()|first }}"
    

    gives

    TASK [debug] **********************************************************************************
    ok: [localhost] => (item=children) => 
      msg:
      - children:
          attachment:
            results:
            - history: {}
              title: SomeTitle
            - future: {}
              title: OtherTitle
      - history: {}
        title: SomeTitle
    ok: [localhost] => (item=children) => 
      msg:
      - children:
          attachment:
            results:
            - history: {}
              title: SomeTitle
            - future: {}
              title: OtherTitle
      - future: {}
        title: OtherTitle
    

    • The above iteration won't work if there are other dictionaries than children.*. For example,
      results2:
        - parents:
            attachment:
              results:
                - history: {}
                  title: SomeTitle
                - future: {}
                  title: OtherTitle
        - children:
            attachment:
              results:
                - history: {}
                  title: SomeTitle
                - future: {}
                  title: OtherTitle
    

    In this case, convert the list to a dictionary

      results2_keys: "{{ results2|json_query('[][keys(@)]')|flatten(2) }}"
      results2_vals: "{{ results2|json_query('[].*.*.results')|flatten(2) }}"
      results2_dict: "{{ dict(results2_keys|zip(results2_vals)) }}"
    

    gives

      results2_dict:
        children:
        - history: {}
          title: SomeTitle
        - future: {}
          title: OtherTitle
        parents:
        - history: {}
          title: SomeTitle
        - future: {}
          title: OtherTitle
    

    Use this dictionary to iterate subelements

        - debug:
            msg: "{{ item }}"
          loop: "{{ results2_dict|dict2items|subelements('value') }}"
          loop_control:
            label: "{{ item.0.key }}"
    

    gives

    TASK [debug] **********************************************************************************
    ok: [localhost] => (item=parents) => 
      msg:
      - key: parents
        value:
        - history: {}
          title: SomeTitle
        - future: {}
          title: OtherTitle
      - history: {}
        title: SomeTitle
    ok: [localhost] => (item=parents) => 
      msg:
      - key: parents
        value:
        - history: {}
          title: SomeTitle
        - future: {}
          title: OtherTitle
      - future: {}
        title: OtherTitle
    ok: [localhost] => (item=children) => 
      msg:
      - key: children
        value:
        - history: {}
          title: SomeTitle
        - future: {}
          title: OtherTitle
      - history: {}
        title: SomeTitle
    ok: [localhost] => (item=children) => 
      msg:
      - key: children
        value:
        - history: {}
          title: SomeTitle
        - future: {}
          title: OtherTitle
      - future: {}
        title: OtherTitle
    

    • To iterate other structures you can try and modify the json_query queries.

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        results1:
          - children:
              attachment:
                results:
                  - history: {}
                    title: SomeTitle
                  - future: {}
                    title: OtherTitle
    
        results2:
          - parents:
              attachment:
                results:
                  - history: {}
                    title: SomeTitle
                  - future: {}
                    title: OtherTitle
          - children:
              attachment:
                results:
                  - history: {}
                    title: SomeTitle
                  - future: {}
                    title: OtherTitle
    
        results2_keys: "{{ results2|json_query('[][keys(@)]')|flatten(2) }}"
        results2_vals: "{{ results2|json_query('[].*.*.results')|flatten(2) }}"
        results2_dict: "{{ dict(results2_keys|zip(results2_vals)) }}"
    
      tasks:
    
        - debug:
            msg: "{{ item }}"
          loop: "{{ results1|subelements('children.attachment.results') }}"
          loop_control:
            label: "{{ item.0.keys()|first }}"
    
        - debug:
            var: results2_dict
    
        - debug:
            msg: "{{ item }}"
          loop: "{{ results2_dict|dict2items|subelements('value') }}"
          loop_control:
            label: "{{ item.0.key }}"