# How to validate the arguments for a nested dictionary

**URL:** <https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284>\
**Category:** Get Help\
**Tags:** role, documentation, collections\
**Created:** [June 10, 2025, 9:35pm UTC](https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284 "2025-06-10T21:35:08Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![markfaine](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/markfaine/32/7291_2.png) [@markfaine](https://forum.ansible.com/u/markfaine)\
**Post date:** [June 10, 2025, 9:35pm UTC](https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284/1 "2025-06-10T21:35:08Z")

</div>

See: [How to validate the arguments for a nested dictionary · Issue #2665 · ansible/ansible-documentation · GitHub](https://github.com/ansible/ansible-documentation/issues/2665#issue-3098445670)

> SUMMARY
> 
> I can’t find an example in the role argument validation documentation for how to document/validate a dict that contains dicts (nested dict)
> 
> For example:
> 
> {  
> “firewalld\_zones”: {  
> “public”: {  
> “target”: “default”,  
> “services”: [  
> “audit”,  
> “bacula-client”,  
> “ssh”,  
> “snmp”,  
> “ldap”,  
> “ldaps”,  
> “nfs”,  
> “ntp”,  
> “pop3”,  
> “pop3s”,  
> “smtp”,  
> “smtps”,  
> “smtp-submission”,  
> “snmp”,  
> “snmptrap”  
> ],  
> “rich\_rules”: [  
> “rule family="ipv4" source address="192.168.1.0/24" port port="22" protocol="tcp" accept”  
> ],  
> “ports”: [  
> “80/tcp”  
> ],  
> “protocols”: [  
> “tcp”  
> ],  
> “forward”: false,  
> “masquerade”: false,  
> “sources”: [  
> “192.168.1.0/24”  
> ],  
> “source\_ports”: [  
> “80/tcp”  
> ],  
> “interfaces”: [  
> “ens192”  
> ],  
> “forward\_ports”: [  
> {  
> “port”: 443,  
> “proto”: “tcp”,  
> “toport”: 8000,  
> “toaddr”: “10.0.13.0”  
> }  
> ],  
> “icmp-block-inversion”: false,  
> “icmp\_blocks”: [  
> “echo-request”  
> ]  
> }  
> }  
> }
> 
> * * *
> 
> firewalld\_zones:  
> public:  
> target: default  
> services:  
> - audit  
> - bacula-client  
> - ssh  
> - snmp  
> - ldap  
> - ldaps  
> - nfs  
> - ntp  
> - pop3  
> - pop3s  
> - smtp  
> - smtps  
> - smtp-submission  
> - snmp  
> - snmptrap  
> rich\_rules:  
> - rule family=“ipv4” source address=“192.168.1.0/24” port port=“22” protocol=“tcp”  
> accept  
> ports:  
> - 80/tcp  
> protocols:  
> - tcp  
> forward: false  
> masquerade: false  
> sources:  
> - 192.168.1.0/24  
> source\_ports:  
> - 80/tcp  
> interfaces:  
> - ens192  
> forward\_ports:  
> - port: 443  
> proto: tcp  
> toport: 8000  
> toaddr: 10.0.13.0  
> icmp-block-inversion: false  
> icmp\_blocks:  
> - echo-request  
> …
> 
> The problem I’m having is that the name for the option is a key so it will be different every time but it has options as well.
> 
> firewalld\_zones:  
> type: dict  
> description: ‘A dictionary containing the zones to be managed.’  
> options:  
> WHAT\_GOES\_HERE: (can’t be ‘public’, ‘drop’, etc since those are free-form dictionary keys.  
> type: dict  
> …
> 
> [Roles — Ansible Community Documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#specification-format)

---

<div class="post-metadata">

**Author:** ![felixfontein](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/felixfontein/32/7530_2.png) [@felixfontein](https://forum.ansible.com/u/felixfontein)\
**Post date:** [June 11, 2025, 5:06am UTC](https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284/2 "2025-06-11T05:06:32Z")

</div>

First please note that you should format the data as code with three backticks, otherwise it’s pretty much unreadable.

> The problem I’m having is that the name for the option is a key so it will be different every time but it has options as well.

There’s no way to validate that with role (and module/plugin) argument spec validation.

The best way is to rearrange the data to not have a list of dictionaries, where `name` (or something else) is one key in the element dictionaries. Like

```yaml

firewalld_zones:
  - name: public
    target: default
    services:
    ...

```

---

<div class="post-metadata">

**Author:** ![chris](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/chris/32/293_2.png) [@chris](https://forum.ansible.com/u/chris)\
**Post date:** [June 11, 2025, 10:21am UTC](https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284/3 "2025-06-11T10:21:53Z")

</div>

> There’s no way to validate that with role (and module/plugin) argument spec validation.

Why exactly can’t it be done if all the dictionary keys are listed in the argument spec? I think I have done this for a few roles… 🤷‍♀️

---

<div class="post-metadata">

**Author:** ![shertel](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/shertel/32/1241_2.png) [@shertel](https://forum.ansible.com/u/shertel)\
**Post date:** [June 11, 2025, 2:22pm UTC](https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284/4 "2025-06-11T14:22:07Z")

</div>

If the dictionary keys can be anything, they can’t be listed in a static argument spec (for roles or modules). It’s the same limitation described in [New role argument spec doesn't support arbitrary keys · Issue #74001 · ansible/ansible · GitHub](https://github.com/ansible/ansible/issues/74001#issuecomment-805013978), the feature has never existed.

Another option could be validating it’s a dictionary in the argument spec:

```yaml
# meta/argument_specs.yml
argument_specs:
  main:
    firewalld_zones:
      type: dict
      default: {} # documentation only, matches the value in defaults/main.yml

```

and use a dynamic argument spec with a [validate\_argument\_spec](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/validate_argument_spec_module.html) task in the role entrypoint:

```auto
# tasks/main.yml

- name: Validate firewalld_zones
  validate_argument_spec:
    argument_spec: "{{ dynamic_spec }}"
  vars:
    dynamic_spec:
      firewalld_zones:
        type: dict
        options: "{{ dict(firewalld_zones | zip([filewalld_zone_value_spec] * (firewalld_zones | length))) }}"
    filewalld_zone_value_spec:
      type: dict
      options:
        forward:
          type: bool
        forward_ports:
          type: list
          elements: dict
          options:
            port:
              type: int
            proto:
              type: str
              choices:
                - tcp
            toport:
              type: int
            toaddr:
              type: str
        # etc...

```

---

<div class="post-metadata">

**Author:** ![markfaine](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/markfaine/32/7291_2.png) [@markfaine](https://forum.ansible.com/u/markfaine)\
**Post date:** [June 11, 2025, 2:53pm UTC](https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284/5 "2025-06-11T14:53:35Z")

</div>

> First please note that you should format the data as code with three backticks, otherwise it’s pretty much unreadable.

I’m aware. I went to all that effort in the Github issue and a lot of good it did me so I was a bit annoyed when I posted it. Not to take it out on you guys 🙂

Good suggestions, thank you! I will look at them all and see what I can do. I thought about literally adding every possible zone, at least the ones that are OOTB but didn’t like that idea. I also thought of just saying it’s a dictionary and leaving it at that but didn’t like that either. I also considered changing the data structure but was hoping to not have to do that because it means I have to go back over the role and change that where it applies. Could be a lot of work. I was not aware of the dynamic argument spec option, I’ll to read up on that.

---

<div class="post-metadata">

**Author:** ![chris](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/chris/32/293_2.png) [@chris](https://forum.ansible.com/u/chris)\
**Post date:** [June 11, 2025, 3:29pm UTC](https://forum.ansible.com/t/how-to-validate-the-arguments-for-a-nested-dictionary/43284/6 "2025-06-11T15:29:58Z")

</div>

FWIW I have a role that has a [dictionary for Apache VirtualHosts](https://git.coop/webarch/users/-/tree/master?ref_type=heads#example-apache-virtualhost) and a [argument spec for this dictionary](https://git.coop/webarch/users/-/blob/master/meta/apache_virtual_host_argument_specs.yml?ref_type=heads) and the dictionaries are validated using [this task](https://git.coop/webarch/users/-/blob/master/tasks/validate_vhost_dirs.yml?ref_type=heads).
