Testing for nested dict object attributes in a template

I’m triggering some automations by (call_service) events that optionally contain data attributes, i.e. if trigger.event.data.service_data.data.actions is defined, the result of the template should contain trigger.event.data.service_data.data.actions and if undefined, it should be set to an empty list (“[]”).

The problem: Using a simple if…then…else statement for testing the existence of trigger.event.data.service_data.data.actions leads to template errors, if the event doesn’t contain any data attributes at all, complaining about the dict object not having a data attribute:

[...]
{% if trigger.event.data.service_data.data.actions is defined %}
  {{ trigger.event.data.service_data.data.actions }}
{% else %}
  []
{% endif %}
[...]
2024-10-31 18:28:24.382 ERROR (MainThread) [homeassistant.helpers.template] Template variable error: 'dict object' has no attribute 'data' when rendering '{% if trigger.event.data.service_data.data.actions is defined %}

To avoid this, I have to use two nested if…then…else statements, firstly checking the existence of trigger.event.data.service_data.data and secondly checking the existence of trigger.event.data.service_data.data.actions:

automation:
  - id: notify_ha_high
    alias: Notify HA High
    trigger:
      - trigger: event
        event_type: call_service
        event_data:
          domain: notify
          service: ha_high
    action:
      - action: notify.ha_devices
        data:
          message: '{{ trigger.event.data.service_data.message }}'
          data:
            actions: >-
              {% if trigger.event.data.service_data.data is defined %}
                {% if trigger.event.data.service_data.data.actions is defined %}
                  {{ trigger.event.data.service_data.data.actions }}
                {% else %}
                  []
                {% endif %}
              {% else %}
                []
              {% endif %}

Is there a more elegant way to check for the existence of nested dict object attributes, apart from nesting conditional statements for every level of the dict object and unneccessarily needing to duplicate the else clause?

best you’re gonna get most likely

{{ trigger.event.data | selectattr(‘service_data’, ‘defined’) | selectattr(‘service_data.data.actions’, ‘defined’) | list | first | default([]) }}

There’s no nice way to do this

EDIT: This won’t work. Ignore. Need coffee this morning.

what you got is probably the best way to do this

EDIT again:

{{ trigger.event.data.get('service_data', {}).get('data',{}).get('actions',[]) }}
1 Like

Well, that one-liner ist indeed much more elegant, than my clause. :slight_smile: Thank you!

TBH, I don’t really understand, why the default values for the intermediate keys need to be set to {} instead of []? What is the purpose of these {} fallback values?

They are empty dictionaries, which allows you to use the .get method to look for a key. data, service_data are typically populated dictionaries.