A `mapattr` filter for templates

When writing templates that involve lists of sensors I often get stuck because the state is a string and not the type you might expect. So for instance, when working with a list of battery level sensors, instead of being able to do this to find ones which are low:

{{ battery_sensors | selectattr('state', 'lt', 20) | map(attribute='entity_id') | join(', ') }}

You end up having to do something like this since state isn’t actually a number:

{% set ns = namespace(out=[]) %}
{% for s in battery_sensors %}
  {% if s.state | float < 20 %}
    {% set ns.out = ns.out | [s.entity_id] %}
  {% endif %}
{% endfor %}
{{ ns.out | join(', ') }}

Which is obviously a lot more complicated and harder to remember/figure out. Plus I imagine its a lot less efficient since you can’t mutate arrays so you have to make a new one each time to add an element to the end.

Some other examples would be:

  1. Filtering a list of temperature/humidity sensors to only those above/below a threshold
  2. Filtering a list of sensors with a count to only those above 0

Basically any time you have numeric sensors this can come up and be frustrating. Now you can fix the type of the state with a map filter like this:

{{ battery_sensors | map(attribute='state') | map('float') | select('lt', 20) }}

But you lose the context when you do that. You just have a list of states now, you can’t get back to the entity ID.

It would be useful to have a mapattr filter that lets you transform value of an attribute of each item in a list without losing access to the rest of the object for further filtering. So the example at the top could then be solved like this:

{{ battery_sensors | mapattr('state', 'float') | selectattr('state', 'lt', 20) | map(attribute='entity_id') | join(', ') }}

I imagine this would be useful in other cases as well.