How to replace only a specific entry in a nested dictionary

The source nested dictionary is for instance the following:
vpc:
provider:

  • net_type: ‘db’
    net_ipv4_subnet: ‘10.0.0.0/16’
    net_ipv6_subnet: ‘FD00::/64’
    net_id: 0
    nic_ids:
  • 1111111111
  • 2222222222
  • net_type: ‘dns’
    net_ipv4_subnet: ‘10.1.0.0/16’
    net_ipv6_subnet: ‘FD01::/64’
    net_id: 1
    nic_ids:
  • 3333333333
  • 4444444444
  • net_type: ‘ns’
    net_ipv4_subnet: ‘10.2.0.0/16’
    net_ipv6_subnet: ‘FD02::/64’
    net_id: 2
    nic_ids:
  • 5555555555
  • 6666666666

We need to replace only the whole entry indexed by “net_type: ‘dns’” with:

  • net_type: ‘dns’
    net_ipv4_subnet: ‘10.3.0.0/16’
    net_ipv6_subnet: ‘FD03::/64’
    net_id: 3
    nic_ids:

so the result would look like:
vpc:
provider:

  • net_type: ‘db’
    net_ipv4_subnet: ‘10.0.0.0/16’
    net_ipv6_subnet: ‘FD00::/64’
    net_id: 0
    nic_ids:
  • 1111111111
  • 2222222222
  • net_type: ‘dns’
    net_ipv4_subnet: ‘10.3.0.0/16’
    net_ipv6_subnet: ‘FD03::/64’
    net_id: 3
    nic_ids:
  • net_type: ‘ns’
    net_ipv4_subnet: ‘10.2.0.0/16’
    net_ipv6_subnet: ‘FD02::/64’
    net_id: 2
    nic_ids:
  • 5555555555
  • 6666666666

I’ve tried the following task using the combine filter:

  • name: Combining 2 dictionaries with combine/list_merge=‘replace’
    vars:
    vpc:
    provider:
  • net_type: ‘db’
    net_ipv4_subnet: ‘10.0.0.0/16’
    net_ipv6_subnet: ‘FD00::/64’
    net_id: 0
    nic_ids:
  • 1111111111
  • 2222222222
  • net_type: ‘dns’
    net_ipv4_subnet: ‘10.1.0.0/16’
    net_ipv6_subnet: ‘FD01::/64’
    net_id: 1
    nic_ids:
  • 3333333333
  • 4444444444
  • net_type: ‘ns’
    net_ipv4_subnet: ‘10.2.0.0/16’
    net_ipv6_subnet: ‘FD02::/64’
    net_id: 2
    nic_ids:
  • 5555555555
  • 6666666666
    net_id: 3
    net_ipv4_subnet: ‘10.3.0.0/16’
    net_ipv6_subnet: ‘FD03::/64’
    new_vpc: “{{ vpc | combine({‘provider’: [{‘net_type’: ‘dns’, ‘net_id’: net_id, ‘net_ipv4_subnet’: net_ipv4_subnet, ‘net_ipv6_subnet’: net_ipv6_subnet, ‘nic_ids’: }]}, recursive=True, list_merge=‘replace’) }}”
    debug:
    msg: “{{ new_vpc }}”

which unfortunately removes the other 2 entries:
ok: [localhost] =>
msg:
provider:

  • net_id: 3
    net_ipv4_subnet: 10.3.0.0/16
    net_ipv6_subnet: FD03::/64
    net_type: dns
    nic_ids:

Same result with community.general.lists_mergeby filter:

  • name: Combining 2 dictionaries with lists_mergeby/list_merge=‘replace’
    vars:
    vpc:
    provider:
  • net_type: ‘db’
    net_ipv4_subnet: ‘10.0.0.0/16’
    net_ipv6_subnet: ‘FD00::/64’
    net_id: 0
    nic_ids:
  • 1111111111
  • 2222222222
  • net_type: ‘dns’
    net_ipv4_subnet: ‘10.1.0.0/16’
    net_ipv6_subnet: ‘FD01::/64’
    net_id: 1
    nic_ids:
  • 3333333333
  • 4444444444
  • net_type: ‘ns’
    net_ipv4_subnet: ‘10.2.0.0/16’
    net_ipv6_subnet: ‘FD02::/64’
    net_id: 2
    nic_ids:
  • 5555555555
  • 6666666666
    seed_vpc:
    provider:
  • net_type: ‘dns’
    net_ipv4_subnet: ‘10.3.0.0/16’
    net_ipv6_subnet: ‘FD03::/64’
    net_id: 3
    nic_ids:
    new_vpc: “{{ [vpc.provider, seed_vpc.provider] | community.general.lists_mergeby(‘net_type=dns’, recursive=True, list_merge=‘replace’) }}”
    debug:
    msg: “{{ new_vpc }}”

leads to:
ok: [localhost] =>
msg:
provider:

  • net_id: 3
    net_ipv4_subnet: 10.3.0.0/16
    net_ipv6_subnet: FD03::/64
    net_type: dns
    nic_ids:

Am I missing something or is it impossible to perform with the currently available ansible filters?

---
- name: Replace dict in list example
  hosts: localhost
  gather_facts: false
  vars:
    vpc:
      provider:
        - net_type: 'db'
          net_ipv4_subnet: '10.0.0.0/16'
          net_ipv6_subnet: 'FD00::/64'
          net_id: 0
          nic_ids:
            - 1111111111
            - 2222222222
        - net_type: 'dns'
          net_ipv4_subnet: '10.1.0.0/16'
          net_ipv6_subnet: 'FD01::/64'
          net_id: 1
          nic_ids:
            - 3333333333
            - 4444444444
        - net_type: 'ns'
          net_ipv4_subnet: '10.2.0.0/16'
          net_ipv6_subnet: 'FD02::/64'
          net_id: 2
          nic_ids:
            - 5555555555
            - 6666666666
    fix:
      net_type: 'dns'
      net_ipv4_subnet: '10.3.0.0/16'
      net_ipv6_subnet: 'FD03::/64'
      net_id: 3
      nic_ids: []
  tasks:
    - name: Do this if the order doesn't matter
      # N.B. This assumes there is 1 provider to replace.
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop:
        - "{{ [vpc.provider | selectattr('net_type', 'ne', fix.net_type)] | sum(start=[fix]) }}"

    - name: Do this if the order matters
      # N.B. This will replace _all_ "dns" providers with 'fix'.
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop:
        - |
          {% set providers = [] %}
          {% for p in vpc.provider %}
          {%   set _ = providers.append(p if p.net_type != fix.net_type else fix) %}
          {% endfor %}{{ providers }}

Very nice :slight_smile:
As it turns out, there is only one entry of type ‘dns’ , so both solutions work perfectly.