What is implicitly converting a string to an integer in an automation?

At times I want to do a dictionary lookup where the keys (strings, of course) are all digits.

For example, I have a input_text with a (string) value of "12345678":

{% set sensor_value = states('input_text.test_integer' ) %}
sensor_value={{ sensor_value }}
is_string={{ sensor_value is string }}
{% set codes = {"12345678":"Found the key"} %}
{{ codes.get(sensor_value,'Nope, did not find it') }}

Resulting in what I would expect -- the text_sensor is a string, and therefore I can look it up in the dictionary:

sensor_value=12345678
is_string=True

Found the key

Now, do that in an automation:

triggers: []
conditions: []
actions:
  - variables:
      codes:
        "12345678": Found the key
      sensor_value: "{{ states('input_text.test_integer' ) }}"
      is_string: "{{ sensor_value is string }}"
      lookup: "{{ codes.get( sensor_value, 'No, did not find the key') }}"
      force_string: >-
        {{ codes.get(sensor_value|string, 'No, did not find the key by string')
        }}

And then run and look at the trace:

context:
  id: 01KVTE4GFBECCQFQQ06GYVQYFK
  parent_id: 01KVTE4GFBCCNX9JY75RCT5GHP
  user_id: null
codes:
  '12345678': Found the key
sensor_value: 12345678
is_string: false        <---- Now it's not a string
lookup: No, did not find the key
force_string: Found the key

Are the templates pre-processed before parsing the JSON and the Python JSON parser implicitly turns it into an integer/float? Or is there some other code that is turning it into an integer?

It is a side effect of how templates are handled. Templates themselves are always strings, which would make them incapable of returning any other type. That is why when they represent a numerical value or compound object, they are converted to that (but not datetime by the way).

Besides forcing as you did, an often used workaround is to return a compound object, because inside that types re kept as they are. So:

sensor_value: '{{ { "key": states("input_text.test_integer") } }}'

should keep key a string no matter what. And to get it:

original_string: >-
        {{ codes.get(sensor_value.key, "You should not see this default")
        }}

You could also return an array, it would also keep the type of the elements inside.

When using only the template inside the template tester of the developer tools, you get to see the type of the data the template will convert to. If you put other stuff around it, the output will of course always be string because it is no longer a plain type.

Ok, there's some code somewhere that renders the template and then the value returned matches a regex for an integer and then converts to integer (or float, I assume).

And my dev tools version only does:

{% set sensor_value = states('input_text.test_integer' ) %}

Which is setting the value directly, not within a {{ }} template.

Thanks.

It's a post process when assigning the template result to a variable. I.e. it happens here:

So when the next variable uses it, this fails:

If you want to force a string, return complex types.

  - variables:
      codes:
        "12345678": Found the key
      sensor: "{{ dict(state=states('input_text.test_integer')) }}"
      is_string: "{{ sensor.state is string }}"
      lookup: "{{ codes.get(sensor.state, 'No, did not find the key') }}"

Which is what I said 30 minutes ago :wink:

No disrespect, was clarifying exactly where it happens

Wasn't offended, hence the :wink:.

I wonder how many places I've done this, which apparently is not needed.

  - variables:
      my_integer: "{{ states('sensor.foo')|int }}"

Or worse... :wink:

  - variables:
      my_string: "{{ states('input_text.test_integer' )|string }}"

Yes, everyone has done it. Whenever I help people, I just remove them if they are unnecessary

BTW, comma-separated strings are also converted, to array form, even string of strings :slight_smile:

variables:
  var1: "{{ '1, 2, 3' }}"
  var2: "{{ '1, ' ~ '2, ' ~ '3' }}"
  var3: 1, 2, 3
  var4: "{{ '\"one\", \"two\", \"three\"' }}"
  var5: one, two, three
  var6: "{{ 'one, two, three' }}"
  results: |
    var1: {{ typeof(var1) }}
    var2: {{ typeof(var2) }}
    var3: {{ typeof(var3) }}
    var4: {{ typeof(var4) }}
    var5: {{ typeof(var5) }}
    var6: {{ typeof(var6) }}
var1:
  - 1
  - 2
  - 3
var2:
  - 1
  - 2
  - 3
var3: 1, 2, 3
var4:
  - one
  - two
  - three
var5: one, two, three
var6: one, two, three
results: |-
  var1: TupleWrapper
  var2: TupleWrapper
  var3: NodeStrClass
  var4: TupleWrapper
  var5: NodeStrClass
  var6: str

WTH is NodeStrClass?! :smiley:

it's the result of a yaml field

At some point, HA swapped to annotatedyaml library, which tracks where yaml exists in files. This is a byproduct of the way that library is used in HA.