Question about arrays in templates - keys() - how to find key if I know the value?

ok, so I know, that it’s easy to define an array:

{% set arrayName = { 'key1':'value1', 'key2':'value2', 'key3':'value2' } %}

I can get the values with no problem, by calling the keys like:

{{ arrayName['key1'] }}

but could it be done the other way? I mean calling the value to get (array of) matching keys? I just run out of the keywords to search, and trial-n-error mode of my copy-paste programming didn’t work also :wink:

First, that’s not an array. That’s a dictionary. To be technical, arrays don’t exist in python unless you are using numpy. The 2 array-like objects are refered to as lists and tuples.

tuples and lists are collections of objects that can be accessed through indexing. Both are similar but lists have more functionalities than tuples. The big take away here is that everything is in order. Whatever you put in it will stay in that order.

tuple:

{% set foo = ( 'a', 'b', 'c' ) %}
{{ foo[1] }}  #returns 'b'

list:

{% set foo = ['a','b','c'] %}
{{ foo[1] }} # returns 'b'

dictionary is an object where you can access the items with an identifier instead of using an indexer. Also, there is no order for the objects. It would be like a bag of fruit. You go in and grab anything by name, like an apple.

{% set fruit = { 'apple':'value1', 'orange':'value2', 'grape':'value3' } %}
{{ fruit['apple'] }} #returns 'value1'

Now to answer your question:

It is not possible to get a list of items equal to your value. It would be possible with an extension, but that requires changes to HA. The best you can do is get the first item out.

{% set fruit = { 'apple':'value', 'orange':'value', 'grape':'value' } %}
{% set keys = fruit.keys() | list %}
{% set values = fruit.values() | list %}
{{ keys[values.index('value')] }} #returns apple

The drawback is that it will only return the first key equal to value, not all the values equal to value.

4 Likes

You might be able to do this, but it depends on what you ultimately want to do with the values. E.g., the following will find all the keys for elements whose value is “value2”:

{% for key,item in arrayName.items() if item=='value2' -%}
  {% if not loop.first %},{% endif %}{{ key }}
{%- endfor %}

But being a Jinja template, it can only return a single string. So if you’re looking for a real “array” (i.e., list), then this can’t do it.

Could put this in a macro and return a string then split the string… For some reason that skipped my mind.

{% set fruit = { 'apple':'value', 'orange':'value', 'grape':'blahblahblah' } %}
{% macro getkeys(mydict, myvalue) %}
  {%- for key,item in mydict.items() if item==myvalue -%}
    {%- if not loop.first %},{% endif %}{{ key }}
  {%- endfor %}
{% endmacro %}
{{ getkeys(fruit, 'value').strip().split(',') }}  #returns ['apple', 'orange']

Edited this to use @pnbruckner’s loop cause it’s better

whoa, I don’t even know where to start… :smiley:

@petro - THANK YOU, once again for an educational-type of answer. I am using some tips’n’tricks that I learned from your earlier comments to my posts and they all work awesome. yeah, as you already know - I’m not a programmer or developer, but “touched” few languages, and my habits & naming comes straight from there. if something was similar to array from othe lang, I just called it array, forgive me! now I know more [and as of now - I already know I’ll use tuples & lists in some of my badly-written automations, just to pimp them up!]. and yes, I knew about returning the first key - tried to build some loops around it, but failed, and that’s why I wrote the post :slight_smile:

@pnbruckner THANKS for the little loop too! just three lines, but already couple of things I need to check and understand correctly, so I would learn how to use it with consciousness, and not only copy+paste :wink: I guess that single string should be ok, I planned to use it in the loop to build entity_ids of booleans to be checked or switched [depends on automation]. should be enough! :slight_smile:

you guys are awesome, if I could check two posts as solutions, that would be the best :slight_smile:

Doesn’t that still just return a string that looks like a list??? If the end use is for specifying entity_id’s, most of those config options are specified as cv.entity_ids which wants a single string of entity_id’s separated by commas, so the extra brackets would break it.

It outputs a string with commas separating the items without brackets. Then the next line after the macro uses the macro and turns it into a list via split(’,’)

Yes, but it’s all one template, and the final result gets turned back into a string. E.g.:

script:
  test:
    sequence:
      service: python_script.test
      data_template:
        input: >
          {% set fruit = { 'apple':'value', 'orange':'value', 'grape':'blahblahblah' } %}
          {% macro getkeys(mydict, myvalue) %}
            {%- for key,item in mydict.items() if item==myvalue -%}
              {%- if not loop.first %},{% endif %}{{ key }}
            {%- endfor %}
          {% endmacro %}
          {{ getkeys(fruit, 'value').strip().split(',') }}

and python_scripts/test.py:

input = data.get('input')
logger.info('length of input is {}'.format(len(input)))

Results in:

2018-11-11 17:20:04 INFO (Thread-20) [homeassistant.components.python_script] Executing test.py: {'input': "['apple', 'orange']"}
2018-11-11 17:20:04 INFO (Thread-20) [homeassistant.components.python_script.test.py] length of input is 19

Well yeah but all op was looking for was a way to filter based on the values instead of the keys. :man_shrugging:

I just played around with this as well and came to another solution, that avoids loops, macros and string-conversions.

{{ fruit.items() | selectattr('1','in','value') | map(attribute='0') | list }}

You can also filter the other way around or for multiple values/keys.

This might not have been possible back then,… :wink:

typing wasn’t possible, it had to be comma separated without list indicators or quotes. Also, please don’t revive old information threads. This was better left in the past. Thanks.