Templating question - passing data

How can I pass a list of data, in this case room names, between scripts? I am trying like this which I do like this:

service: script.blah_blah
data:
  room: ['kitchen', 'sitting_room']

The script (blah_blah) that receives the data needs to pass it on to another script which I am trying to do like this:

service: script_waffle
data_template:
  room: >
   {{ room }}

All the code in both scripts work perfectly in the template editor. I test it there by setting the ‘room’ variable like this (which is how the original script passes it:
{% set room = ['kitchen', 'sitting_room'] %}

But in a ‘live’ environment the second script doesn’t work with the data passed to it like that.

My question is, how can I pass a list from one script to another script so that it stays as a list and not as a simple string, which I suspect is what is happening now ?

Current answer: you can’t.

However, I have experienced the same problem and I have a written a pair of filters that will be in the v.101 release. In the caller, use | to_json and in the script, use | from_json. You can then access the fields of any object (or array) just like when you created it because it’s transported from caller to script as a JSON string.

2 Likes

That’ brilliant thanks.
And perfect timing!

If you want to get your hands on these filters early, and you’re on a build close to current, you can take this file and copy it over the "lib/python3.x/site-packages/homeassistant/helpers/template.py" file in your home assistant installation, and then restart.

@SteveDinn
Ok, so I’m finally finding the time to try this…
I may be being a bit dim but I’m not sure I follow how to use it :blush::thinking::roll_eyes:

I want to pass a list of rooms from an automation to a script.
That script does some stuff and then calls another script passing it that exact same list of rooms.
The last script in the chain will use that list.

So the originating Automation will call script.one like this

service: script.one
data:
  room: ['kitchen', 'sitting_room']

script.one will call script.two:

service: script.two
data_template:
  room: >
   {{ room }}

and script.two will act on the list, room that it has just been passed turning on input_booleans corresponding to the rooms in the list.:

    #=== Set Media Players to 'in use' as appropriate
    - service: input_boolean.turn_on
      data_template:
        entity_id: >
          {#=== If passed rooms then set rooms to 'in use' as appropriate ===#}
          {% if room | length != 0 %}
            {%- for x in room -%}
              input_boolean.announcement_on_{{ x }}_media_player{%- if not loop.last %},
              {%- endif %}
            {%- endfor -%}

So my question is how and where do I use the filters to_json and from_json? Presumably I convert it to JSON in the first automation (but how exactly?) but then how does it stay as JSON as it gets passed down the chain? Do I need to do something like this

room | from_json | to_json

or does it stay as JSON all the way through until the end where I simply do (bearing in mind I need to loop through the list)

room | from_json?

Or what? :slight_smile:
Sorry if this is a stupid question…

First off, doing this:

room | from_json | to_json

has an end result of absolutely nothing. Those two filters do the opposite of each other.

The reason I introduced those filters is because when you pass an object or an array like you’ve created (['kitchen', 'sitting_room']) to a script, the parameter always comes through as a string. If you experience the same thing, you’ll notice pretty quickly that script.two won’t work as you intend.

You’ll be expecting room to be an array, when it will actuall be a string, and not even valid JSON as it uses single quotes and doesn’t escape special characters. I think this is python’s default ‘stringify’ view. The room | length != 0 will work because it’ll return the length of the string, but then the for loop will return the characters in the string one by one. So, I would do the following:

service: script.one
data:
  room: ['kitchen', 'sitting_room'] | to_json  <--- Convert to JSON here

service: script.two
data_template:
  room: >
   {{ room }} <--- This will be a string at this point, but you don't do anything with it.

Script one makes sure to convert the parameter to proper JSON before passing it.
Script two doesn’t need to make any changes, because it looks like it’s not even looking at that parameter.

Then the script.two body needs to convert the string back to an array

    #=== Set Media Players to 'in use' as appropriate
    - service: input_boolean.turn_on
      data_template:
        entity_id: >
          {#=== If passed rooms then set rooms to 'in use' as appropriate ===#}
          {%- set room_array = room | from_json -%} <--- Convert back to an array here.
          {% if room_array | length != 0 %}
            {%- for x in room_array -%}
              input_boolean.announcement_on_{{ x }}_media_player{%- if not loop.last %},
              {%- endif %}
            {%- endfor -%}
          {%- endif %}

This parses the “room” string as JSON, which will result in an array that you can access just like you instantiated it locally in script.two. Then you can just replace room with room_array in the if and for clauses, and you should be fine.

Does this make sense?

1 Like

It makes perfect sense. Thank you.
Those first two paragraphs explain a lot.

I have a stupidly over engineered notification system that I want to make more flexible and robust so I need to convert it all to use this. (It’s flexible and robust enough as it is but hey, nothing that is good can’t be made better with some fiddling!)

And yes, it did seem obvious to me that room | from_json | to_json was going to end up doing nothing but I just wasn’t really sure what was happening ‘internally’ to the ‘strings’ in all the passing around…

Just another quick thank you for this.
I haven’t finished but I have enough done to know it is all working well.

1 Like