`undef()` cannot be used on dictionary members

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

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

gives:

PLAY [localhost] ******************

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

Is this supposed to happen? The documentation does not help in this regard, and I have found no mention of the function in the docs of jinja2.

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: {}.

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?

- 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:

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

I think this filter would do what you want? ansible.utils.keep_keys filter – Keep specific keys from a data recursively. — Ansible Community Documentation

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

- 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

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

- 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

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.