# Checking that keys nested in a dictionary are unique using JMESPath (or not)

**URL:** <https://forum.ansible.com/t/checking-that-keys-nested-in-a-dictionary-are-unique-using-jmespath-or-not/736>\
**Category:** Get Help\
**Tags:** php, jmespath, role, playbook\
**Created:** [September 14, 2023, 2:10pm UTC](https://forum.ansible.com/t/checking-that-keys-nested-in-a-dictionary-are-unique-using-jmespath-or-not/736 "2023-09-14T14:10:17Z")\
**Posts on this page:** 4\
**Page:** 1

<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:** [September 14, 2023, 2:10pm UTC](https://forum.ansible.com/t/checking-that-keys-nested-in-a-dictionary-are-unique-using-jmespath-or-not/736/1 "2023-09-14T14:10:17Z")

</div>

I have a PHP role that defines the versions of PHP to install and the FPM pools to configure and enable that looks something like this:

```yaml
php_versions:
  8.1:
    state: present
    pools:
      admin:
        state: present
        listen: /run/php/admin_php-fpm.sock
      www:
        state: present
        listen: /run/php/php8.1-fpm.sock
  8.2:
    state: present
    pools:
      www:
        state: present
        listen: /run/php/php8.2-fpm.sock

```

And for monitoring purposes I have realised that the PHP-FPM pool names need to be unique, there can’t be a `www` pool for more than one version of PHP as there is currently.

What would be the best way to check that the `pools` keys are unique when the PHP version `state` is `present` and the pool `state` is `present` across the PHP versions?

This role is going to have to delete some `www` pools using a config like this:

```yaml
php_versions:
  8.1:
    state: present
    pools:
      admin:
        state: present
        listen: /run/php/admin_php-fpm.sock
      www:
        state: absent
      www81:
        state: present
        listen: /run/php/php8.1-fpm.sock
  8.2:
    state: present
    pools:
      www:
        state: absent
      www82:
        state: present
        listen: /run/php/php8.2-fpm.sock

```

I have got as far as a JMESPath query to generate lists of the pool names (using the first version of `php_versions` above):

```yaml
- name: Debug pool names
  ansible.builtin.debug:
    msg: "{{ php_versions | community.general.json_query('*.pools[].keys(@)') }}"
  when: php_versions is defined

```

This returns:

```auto
ok: [php] => {
    "msg": [
        [
            "www"
        ],
        [
            "admin",
            "www"
        ]
    ]
}

```

I expect I could put something together using loops and `include_tasks` but I was wondering if there would be a better way someone could suggest…

BTW I added the `community` tag here assuming that it was a reference to the `community.general` collection but perhaps it isn’t?

---

<div class="post-metadata">

**Author:** ![gwmngilfen](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/gwmngilfen/32/6_2.png) [@gwmngilfen](https://forum.ansible.com/u/gwmngilfen)\
**Post date:** [September 14, 2023, 2:26pm UTC](https://forum.ansible.com/t/checking-that-keys-nested-in-a-dictionary-are-unique-using-jmespath-or-not/736/2 "2023-09-14T14:26:23Z")

</div>

Indeed not. We’re still working out how to get the best of tags for the collections, given there are so many - @felixfontein how would you handle this, do you think? Is a `community.general` tag warranted?

---

<div class="post-metadata">

**Author:** ![sean\_sullivan](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/sean_sullivan/32/274_2.png) [@sean\_sullivan](https://forum.ansible.com/u/sean_sullivan)\
**Post date:** [September 14, 2023, 3:29pm UTC](https://forum.ansible.com/t/checking-that-keys-nested-in-a-dictionary-are-unique-using-jmespath-or-not/736/3 "2023-09-14T15:29:23Z")

</div>

There is going to be 10 ways of doing it, I would suggest a new filter though, based on the [original code](https://github.com/ansible-collections/community.general/blob/main/plugins/filter/json_query.py)  
[Guide on creating a new filter](https://www.ansible.com/blog/ansible-data-manipulation-with-a-filter)

It is possible to do this in multiple tasks, loops, and various things,

When I tried to run your play I got the following out though

```auto
 {
    "msg": [
        [
            "admin",
            "www",
            "www81"
        ],
        [
            "www",
            "www82"
        ]
    ]
}

```

in addition I believe the community.general tag would likely be needed as community or general itself are very generic, but in our world, community.general is generally understood

---

<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:** [September 14, 2023, 9:32pm UTC](https://forum.ansible.com/t/checking-that-keys-nested-in-a-dictionary-are-unique-using-jmespath-or-not/736/4 "2023-09-14T21:32:21Z")

</div>

Thanks for loking at this @sean_sullivan , the reason you got a different output is because you used a different input, the output I posted was for the first example of `php_versions` I provided and your output was for the second, sorry my first post wasn’t clear, I edited it to add the second example when I realised that it’s fine to have duplicated pools and versions that are absent — only present ones need to be checked for duplicates.

The JMESPath expression can be improved to only match versions of PHP that are set to be present (using the second version of `php_versions` above):

```yaml
- name: Debug pool names
  ansible.builtin.debug:
    msg: "{{ php_versions | community.general.json_query('*|[?state==`present`].pools') }}"

```

This result in:

```json
[
  {
    "admin": {
      "state": "present",
      "listen": "/run/php/admin_php-fpm.sock"
    },
    "www": {
      "state": "absent"
    },
    "www81": {
      "state": "present",
      "listen": "/run/php/php8.1-fpm.sock"
    }
  },
  {
    "www": {
      "state": "absent"
    },
    "www82": {
      "state": "present",
      "listen": "/run/php/php8.2-fpm.sock"
    }
  }
]

```

However I can’t work out how to extract the keys for the pools that are `present`, so looping through this list and including another file:

```yaml
    - name: Loop through the PHP-FPM versions
      ansible.builtin.include_tasks: fpm_pool_checks.yml
      loop: "{{ php_versions | community.general.json_query('*|[?state==`present`].pools') }}"
      loop_control:
        loop_var: php_pool

```

With `fpm_pool_checks.yml` containing another loop that sets a fact to create a list of pool names:

```yaml
- name: Set a fact for the pools that are present
  ansible.builtin.set_fact:
    php_pool_names: "{{ php_pool_names + [php_pool_vars.key] }}"
  when: php_pool_vars.value.state == "present"
  loop: "{{ php_pool | dict2items }}"
  loop_control:
    loop_var: php_pool_vars

```

Then in the first file a check can be made for duplicates:

```yaml
- name: Check that no PHP-FPM pool names are duplicated
  ansible.builtin.assert:
    that:
      - php_pool_names | length == php_pool_names | unique | length
    fail_msg: |-
      PHP-FPM pool names should be unique
      {% for php_pool_n in php_pool_names %} {{php_pool_n }}{% endfor %}

```

If there are duplicates the role fails:

```auto
TASK [php : Check that no PHP-FPM pool names are duplicated] **************************************************************************
fatal: [php]: FAILED! => changed=false 
  assertion: php_pool_names | length == php_pool_names | unique | length
  evaluated_to: false
  msg: 'PHP-FPM pool names should be unique: www81 www admin www'

```

Perhaps this is one of the least elegant of the _“10 ways of doing it”_ … if anyone can suggest a solution that doesn’t need a couple of loops and an `include_tasks` I’d be interested to hear it 🙂 .

With hindsight I’d have used lists for the PHP versions and pools rather than a dictionary as they are easier to manipulate, at the time I used a dictionary because they look nicer…
