# \`undef()\` cannot be used on dictionary members

**URL:** <https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879>\
**Category:** Get Help\
**Tags:** variables, jinja2\
**Created:** [November 13, 2024, 12:58pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879 "2024-11-13T12:58:28Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![pieterjanv](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/pieterjanv/32/5401_2.png) [@pieterjanv](https://forum.ansible.com/u/pieterjanv)\
**Post date:** [November 13, 2024, 12:58pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/1 "2024-11-13T12:58:28Z")

</div>

`undef()` behaves unexpectedly when set to a dictionary member. I expected the following code to ensure that more or less `foo == {}`.

```yaml
- hosts: localhost
  gather_facts: no
  vars:
    foo:
      bar: baz
  tasks:
    - vars:
        foo:
          bar: "{{ undef() }}"
      debug:
        var: foo

```

gives:

```auto
PLAY [localhost] ******************

TASK [debug] *****************
ok: [localhost] => {
    "foo": "VARIABLE IS NOT DEFINED!"
}

```

Is this supposed to happen? The [documentation](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_templating_undef.html) does not help in this regard, and I have found no mention of the function in the docs of jinja2.

---

<div class="post-metadata">

**Author:** ![flowerysong](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/flowerysong/32/2305_2.png) [@flowerysong](https://forum.ansible.com/u/flowerysong)\
**Post date:** [November 13, 2024, 2:45pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/2 "2024-11-13T14:45:12Z")

</div>

This is the expected behaviour. You have only partially defined the dictionary (at least one value is undefined, because you have explicitly set it to an undefined value) so it is treated as undefined

If, as you’ve stated, you want `foo` to be equal to `{}`, you should just do `foo: {}`.

---

<div class="post-metadata">

**Author:** ![pieterjanv](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/pieterjanv/32/5401_2.png) [@pieterjanv](https://forum.ansible.com/u/pieterjanv)\
**Post date:** [November 13, 2024, 3:03pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/3 "2024-11-13T15:03:24Z")

</div>

I see. In this case there is a trivial alternative as you point out, but not when I’m trying to undefine a particular member of a dictionary.

Is there any expression in place of `???` that would unset `foo.k2`, but leave any other keys intact?

```yaml
- hosts: localhost
  gather_facts: no
  vars:
    foo:
      k1: v1
      k2: v2
  tasks:
    - vars:
        foo: "{{ ??? }}" # should unset foo.k2
      debug:
        var: foo

```

Being explicit about which keys to carry over does not work in the obvious way, because of lazy evaluation:

```yaml
- vars:
    foo:
      k1: "{{ foo.k1 }}" # recursion error

```

---

<div class="post-metadata">

**Author:** ![mikemorency](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mikemorency/32/2672_2.png) [@mikemorency](https://forum.ansible.com/u/mikemorency)\
**Post date:** [November 14, 2024, 2:18pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/4 "2024-11-14T14:18:37Z")

</div>

I think this filter would do what you want? [ansible.utils.keep\_keys filter – Keep specific keys from a data recursively. — Ansible Community Documentation](https://docs.ansible.com/ansible/latest/collections/ansible/utils/keep_keys_filter.html)

---

<div class="post-metadata">

**Author:** ![pieterjanv](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/pieterjanv/32/5401_2.png) [@pieterjanv](https://forum.ansible.com/u/pieterjanv)\
**Post date:** [November 15, 2024, 6:33pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/5 "2024-11-15T18:33:47Z")

</div>

Thanks. First time encountering that filter, but applying it in the obvious way would result in a recursion error:

```yaml
- hosts: localhost
  gather_facts: no
  vars:
    foo:
      k1: v1
      k2: v2
  tasks:
    - vars:
        foo: "{{ foo | ansible.utils.keep_keys(target=['k1']) }}"
      debug:
        var: foo # recursion error

```

---

<div class="post-metadata">

**Author:** ![mikemorency](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/mikemorency/32/2672_2.png) [@mikemorency](https://forum.ansible.com/u/mikemorency)\
**Post date:** [November 15, 2024, 6:50pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/6 "2024-11-15T18:50:28Z")

</div>

I think working around the recursion depends on the rest of your playbook/use case. Heres one solution

```auto
- hosts: localhost
  gather_facts: no
  vars:
    _foo:
      k1: v1
      k2: v2
    foo: "{{ _foo }}"
  tasks:
    - vars:
        foo: "{{ _foo | ansible.utils.keep_keys(target=['k1']) }}"
      debug:
        var: foo

```

---

<div class="post-metadata">

**Author:** ![pieterjanv](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/pieterjanv/32/5401_2.png) [@pieterjanv](https://forum.ansible.com/u/pieterjanv)\
**Post date:** [November 15, 2024, 7:36pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/7 "2024-11-15T19:36:53Z")

</div>

Perhaps this or evaluating something using set\_fact to reassign the actual value will get you out of most situations, but it feels tedious. But I suppose that’s more about how to elegantly work around the (to me unexpected) properties of lazy evaluation, which is for another topic. Thanks.

---

<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:** [November 18, 2024, 5:46pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/8 "2024-11-18T17:46:58Z")

</div>

This isn’t an issue with lazy variable evaluation, you just cannot refer to a variable to define itself. Use the same variable name to fully override variables defined with lesser precedence, otherwise you should use intermediate var(s).

```yaml
- hosts: localhost
  gather_facts: no
  vars:
    foo: "{{ _foo | dict2items | rejectattr('key', 'in', foo_exclude) | items2dict }}"
    _foo:
      k1: v1
      k2: v2
    foo_exclude: []
  tasks:
    - debug: msg="{{ foo }}"

    - debug: msg="{{ foo }}"
      vars:
        foo_exclude: [k2]

```

---

<div class="post-metadata">

**Author:** ![pieterjanv](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/pieterjanv/32/5401_2.png) [@pieterjanv](https://forum.ansible.com/u/pieterjanv)\
**Post date:** [November 18, 2024, 7:59pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/9 "2024-11-18T19:59:22Z")

</div>

I suppose that’s right, and I was mistaking expressions like (JavaScript)

```javascript
foo = foo; // works

```

which works just fine if `foo` has been defined, for definitions like (again JavaScript):

```javascript
let foo = foo; // does not work

```

which will not work.

What I’m after is a way to define a var called `foo` in terms of whatever `foo` happens to be in the surrounding scope, which can happen if some role I want to use happens to have a parameter with a name that is already used. Intermediate facts are the most reliable way to do this I suppose.

But if not for lazy evaluation, there seems to be no reason to require the mention of `foo` in `foo: "{{ foo }}"` to refer to the variable being defined, as opposed to the value it has at that point. Although this makes me wonder why JavaScript doesn’t work that way.

---

<div class="post-metadata">

**Author:** ![sivel](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/sivel/32/1198_2.png) [@sivel](https://forum.ansible.com/u/sivel)\
**Post date:** [November 18, 2024, 9:20pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/10 "2024-11-18T21:20:15Z")

</div>

> [@pieterjanv](#):
>
> What I’m after is a way to define a var called `foo` in terms of whatever `foo` happens to be in the surrounding scope

I think that is a common misconception of the way templating happens. Variable precedence happens before templating, and the variable precedence maintains copies of the var at all different levels it is defined, and precedence does not preserve any context about lower levels of precedence.

So defining `foo: "{{ foo }}"` at any level, is a self referential assignment, not referencing lower precedence levels.

Effectively we just go through the precedence hierarchy, and merge keys over top of each other, _then_ we pass that on to templating after the merging.

---

<div class="post-metadata">

**Author:** ![pieterjanv](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/pieterjanv/32/5401_2.png) [@pieterjanv](https://forum.ansible.com/u/pieterjanv)\
**Post date:** [November 19, 2024, 9:32pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/11 "2024-11-19T21:32:15Z")

</div>

Thanks for clarifying. Sounds like something that may be hard to change, though it seems it would be worthwhile otherwise. I’d love to be able to treat roles like functions, and this is one of the ways the analogy breaks; defining role vars is not like passing arguments to a function in an important way.

I did find a way to wrap `include_role` in a plugin that eagerly evaluates its parameters and assigns the one called `input` to `include_role`’s vars, which I believe gets me what I want. I have some reservations about the particular way I went about wrapping `include_role`, but I guess the more important question is what I’m giving up by using eager evaluation. I’m thinking the trade-off might be a simpler mental model for less static analysis and more overhead. I suppose I’m stretching this topic a little, but any comments on this would be much appreciated.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/ansible/original/1X/1882d9a01da5d8996a862359b0913cc568a39b84.svg) [@system](https://forum.ansible.com/u/system)\
**Post date:** [December 19, 2024, 9:32pm UTC](https://forum.ansible.com/t/undef-cannot-be-used-on-dictionary-members/10879/12 "2024-12-19T21:32:57Z")

</div>

Closed on December 19, 2024, 9:32pm UTC

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
