Search code examples
ansiblejinja2ansible-2.xansible-inventory

Combine multiple dict in a single list of dicts based on the key value matches in Ansible


I would like to combine multiple dict in a list of dicts based on multiple key/value matches.

To be precise: match key value "ru" & "code", if it matches combine the dict and add "usg_amt" of mutiple dict that matches.

Input

{
    "cis": [
        {
            "Id": "388",
            "type": "usage",
            "properties": {
                "usg_amt": "144",
                "ru":"123.01",
                "code":"2236"
           }
        },
                                   {
            "Id": "389",
            "type": "usage",
            "properties": {
                "usg_amt": "82",
                "ru":"123.01",
                "code":"2236"
           }
        },
        {
            "Id": "19",
            "type": "usage",
            "properties": {
                "usg_amt": "12",
                "ru":"124.01",
                "code":"2235"
           }
        }
]
}

Expected

{
    "cis": [
        {
            "Id": "388",
            "type": "usage",
            "properties": {
                "usg_amt": "226", ###### 82+144
                "ru":"123.01",
                "code":"2236"
           }
        },
        {
            "Id": "19",
            "type": "usage",
            "properties": {
                "usg_amt": "12",
                "ru":"124.01",
                "code":"2235"
           }
        }
]
}

Code

But the below code is only giving a single dict as output.

 - name: Combine the dictionaries that have matching age
       set_fact:
          combined_list: "{{ datacreate.cis | combine({'key': ['ru', 'code']}) }}"

Solution

  • Q: "Match attributes ru and code. If they match combine the dictionaries."

    A: Create the list of the indexes cis_index and combine the dictionaries

      cis_index: "{{ cis|json_query(cis_index_query) }}"
      cis_index_query: '[].{index: join(`_`, [properties.ru, properties.code])}'
      cis_indexs: "{{ cis|zip(cis_index)|map('combine') }}"
    

    give

      cis_index:
      - index: '123.01_2236'
      - index: '123.01_2236'
      - index: '124.01_2235'
    
      cis_indexs:
        - Id: '388'
          index: '123.01_2236'
          properties: {code: '2236', ru: '123.01', usg_amt: '144'}
          type: usage
        - Id: '389'
          index: '123.01_2236'
          properties: {code: '2236', ru: '123.01', usg_amt: '82'}
          type: usage
        - Id: '19'
          index: '124.01_2235'
          properties: {code: '2235', ru: '124.01', usg_amt: '12'}
          type: usage
    

    Group the items of the list by index

      cis_groups: "{{ cis_indexs|groupby('index') }}"
    

    gives

      cis_groups:
        - - '123.01_2236'
          - - Id: '388'
              index: '123.01_2236'
              properties: {code: '2236', ru: '123.01', usg_amt: '144'}
              type: usage
            - Id: '389'
              index: '123.01_2236'
              properties: {code: '2236', ru: '123.01', usg_amt: '82'}
              type: usage
        - - '124.01_2235'
          - - Id: '19'
              index: '124.01_2235'
              properties: {code: '2235', ru: '124.01', usg_amt: '12'}
              type: usage
    

    Create the expected structure in Jinja. Fit the template to your needs

      cis_update_str: |
        {% for i in  cis_groups %}
        - {{ i.1|reverse|combine }}
        {% endfor %}
      cis_update: "{{ cis_update_str|from_yaml }}"
    

    gives

      cis_update:
        - Id: '388'
          index: '123.01_2236'
          properties: {code: '2236', ru: '123.01', usg_amt: '144'}
          type: usage
        - Id: '19'
          index: '124.01_2235'
          properties: {code: '2235', ru: '124.01', usg_amt: '12'}
          type: usage
    

    • Example of a complete playbook for testing
    - hosts: localhost
    
      vars:
    
        cis:
          - Id: '388'
            properties: {code: '2236', ru: '123.01', usg_amt: '144'}
            type: usage
          - Id: '389'
            properties: {code: '2236', ru: '123.01', usg_amt: '82'}
            type: usage
          - Id: '19'
            properties: {code: '2235', ru: '124.01', usg_amt: '12'}
            type: usage
    
        cis_index: "{{ cis|json_query(cis_index_query) }}"
        cis_index_query: '[].{index: join(`_`, [properties.ru, properties.code])}'
        cis_indexs: "{{ cis|zip(cis_index)|map('combine') }}"
        cis_groups: "{{ cis_indexs|groupby('index') }}"
        cis_update_str: |
          {% for i in  cis_groups %}
          - {{ i.1|reverse|combine }}
          {% endfor %}
        cis_update: "{{ cis_update_str|from_yaml }}"
        cis_updat2: "{{ cis_groups|map('last')|map('reverse')|map('combine') }}"
    
      tasks:
    
        - debug:
            var: cis|to_yaml
        - debug:
            var: cis_index
        - debug:
            var: cis_indexs|to_yaml
        - debug:
            var: cis_groups|to_yaml
        - debug:
            var: cis_update|to_yaml
        - debug:
            var: cis_updat2|to_yaml
    

    Q: "Sum all attributes usg_amt."

    A: Fit the template to your needs

      cis_update_str: |
        {% for i in  cis_groups %}
        {% set usg_amt = i.1|map(attribute='properties.usg_amt')|map('int')|sum %}
        - {{ i.1|reverse|combine([{'properties': {'usg_amt': usg_amt}}], recursive=true) }}
        {% endfor %}
      cis_update: "{{ cis_update_str|from_yaml }}"
    

    gives

      cis_update:
        - Id: '388'
          index: '123.01_2236'
          properties: {code: '2236', ru: '123.01', usg_amt: 226}
          type: usage
        - Id: '19'
          index: '124.01_2235'
          properties: {code: '2235', ru: '124.01', usg_amt: 12}
          type: usage
    

    Use the filter ansible.utils.remove_keys if you want to remove the attribute index from the result


    Q: "This code is not adding the decimal values and it's ignoring all the usg_amt values which are having decimals."

    A: Fit the template to your needs

        cis_update_str: |
          {% for i in  cis_groups %}
          {% set usg_amt_list = i.1|map(attribute='properties.usg_amt') %}
          {% set sum_float = usg_amt_list|select('search', '\.')|length > 0 %}
          {% if sum_float %}
          {% set usg_amt = usg_amt_list|map('float')|sum %}
          {% else %}
          {% set usg_amt = usg_amt_list|map('int')|sum %}
          {% endif %}
          - {{ i.1|reverse|combine([{'properties': {'usg_amt': usg_amt}}], recursive=true) }}
          {% endfor %}
        cis_update: "{{ cis_update_str|from_yaml }}"
    

    Given the data to test floats (decimals)

      cis:
        - Id: '388'
          properties: {code: '2236', ru: '123.01', usg_amt: '144'}
          type: usage
        - Id: '389'
          properties: {code: '2236', ru: '123.01', usg_amt: '82'}
          type: usage
        - Id: '19'
          properties: {code: '2235', ru: '124.01', usg_amt: '12'}
          type: usage
        - Id: '20'
          properties: {code: '2237', ru: '125.01', usg_amt: '1.10'}
          type: usage
        - Id: '21'
          properties: {code: '2237', ru: '125.01', usg_amt: '1.20'}
          type: usage
    

    Update the attribute usg_amt with float if any of the items is float

      cis_update:
        - Id: '388'
          index: '123.01_2236'
          properties: {code: '2236', ru: '123.01', usg_amt: 226}
          type: usage
        - Id: '19'
          index: '124.01_2235'
          properties: {code: '2235', ru: '124.01', usg_amt: 12}
          type: usage
        - Id: '20'
          index: '125.01_2237'
          properties: {code: '2237', ru: '125.01', usg_amt: 2.3}
          type: usage
    

    In the input list cis the values of the attribute usg_amt are strings. If you want to keep the strings also in the updated list convert the integers, or floats to strings

          - {{ i.1|reverse|combine([{'properties': {'usg_amt': usg_amt|string}}], recursive=true) }}
    

    gives

      cis_update:
        - Id: '388'
          index: '123.01_2236'
          properties: {code: '2236', ru: '123.01', usg_amt: '226'}
          type: usage
        - Id: '19'
          index: '124.01_2235'
          properties: {code: '2235', ru: '124.01', usg_amt: '12'}
          type: usage
        - Id: '20'
          index: '125.01_2237'
          properties: {code: '2237', ru: '125.01', usg_amt: '2.3'}
          type: usage
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        cis:
          - Id: '388'
            properties: {code: '2236', ru: '123.01', usg_amt: '144'}
            type: usage
          - Id: '389'
            properties: {code: '2236', ru: '123.01', usg_amt: '82'}
            type: usage
          - Id: '19'
            properties: {code: '2235', ru: '124.01', usg_amt: '12'}
            type: usage
          - Id: '20'
            properties: {code: '2237', ru: '125.01', usg_amt: '1.10'}
            type: usage
          - Id: '21'
            properties: {code: '2237', ru: '125.01', usg_amt: '1.20'}
            type: usage
    
        cis_index: "{{ cis|json_query(cis_index_query) }}"
        cis_index_query: '[].{index: join(`_`, [properties.ru, properties.code])}'
        cis_indexs: "{{ cis|zip(cis_index)|map('combine') }}"
        cis_groups: "{{ cis_indexs|groupby('index') }}"
        cis_update_str: |
          {% for i in  cis_groups %}
          {% set usg_amt_list = i.1|map(attribute='properties.usg_amt') %}
          {% set sum_float = usg_amt_list|select('search', '\.')|length > 0 %}
          {% if sum_float %}
          {% set usg_amt = usg_amt_list|map('float')|sum %}
          {% else %}
          {% set usg_amt = usg_amt_list|map('int')|sum %}
          {% endif %}
          - {{ i.1|reverse|combine([{'properties': {'usg_amt': usg_amt|string}}], recursive=true) }}
          {% endfor %}
        cis_update: "{{ cis_update_str|from_yaml }}"
    
      tasks:
    
        - debug:
            var: cis|to_yaml
        - debug:
            var: cis_index
        - debug:
            var: cis_indexs|to_yaml
        - debug:
            var: cis_groups|to_yaml
        - debug:
            var: cis_update|to_yaml