MQTT Trigger - Access of nested JSON Dictionary

Hello, i am using an MQTT trigger and am trying to access nested values in the below example MQTT message. Apologies if i don’t get the terms quite right.

within ‘Slots’ there are 2 entities. ‘Lights’ and ‘Light_State’. in the example message I wish to access the nested values e.g ‘landing’ for ‘lights’ and the value ‘on’ for ‘light_state’

I can access the values ‘sometimes’ using the following code snippets:-

{% set var2 = trigger.payload_json.slots[0].value.value %}
{% if trigger.payload_json.slots[1].value.value == "on" %}

The problem, is that in the message Lights and Light_state can swap position within Slots. So Light_State may be at position [0] or position [1]

So the question is whether there another way i access these entities’ values without using the [0] or [1] index?

Thanks!

Example MQTT message:-

{
"input": "turn the landing on", 
"intent": 
	{
	"intentName": "ChangeLightState", 
	"confidenceScore": 0.75},
	"siteId": "prometheus", 
	"id": null, 
	"slots": [
				{
					"entity": "lights", 
					"value": 
						{
							"kind": "Unknown",
							"value": "landing"
						}, 
					"slotName": "light_name", 
					"rawValue": "landing", 
					"confidence": 1.0, 
					"range": 
						{
							"start": 9, 
							"end": 16, 
							"rawStart": 9, 
							"rawEnd": 16
						}
				}, 
				{
					"entity": "light_state", 
					"value": 
						{
							"kind": "Unknown",
							"value": "on"
						}, 
							"slotName": "light_state", 
							"rawValue": "on", 
							"confidence": 1.0, 
							"range": 
								{
									"start": 17,
									"end": 19,
									"rawStart": 17,
									"rawEnd": 19
								}
				}
			],

Not solving your question by ‘not using array indexes’. But extending what you started, paste the code below into the Template Editor in Developer Tools and see if you might be able to work a solution from it:

{% set test = {
"input": "turn the landing on", 
"intent": 
	{
	"intentName": "ChangeLightState", 
	"confidenceScore": 0.75},
	"siteId": "prometheus", 
	"id": null, 
	"slots": [
				{
					"entity": "lights", 
					"value": 
						{
							"kind": "Unknown",
							"value": "landing"
						}, 
					"slotName": "light_name", 
					"rawValue": "landing", 
					"confidence": 1.0, 
					"range": 
						{
							"start": 9, 
							"end": 16, 
							"rawStart": 9, 
							"rawEnd": 16
						}
				}, 
				{
					"entity": "light_state", 
					"value": 
						{
							"kind": "Unknown",
							"value": "on"
						}, 
							"slotName": "light_state", 
							"rawValue": "on", 
							"confidence": 1.0, 
							"range": 
								{
									"start": 17,
									"end": 19,
									"rawStart": 17,
									"rawEnd": 19
								}
				}
			]}
%}
{% if test.slots[0].entity == "lights" %}
{{test.slots[0].entity}}
{{test.slots[0].value.value}}
{% else %}
{{test.slots[1].entity}}
{{test.slots[1].value.value}}
{% endif %}

Uh-oh …

You still need to use the index but first test if trigger.payload_json.slots[0].entity == 'lights', or check if trigger.payload_json.slots[0].slotname == 'light_name', in order to determine what slot 0 contains.


EDIT

Oops! I appear to have duplicated dproffer’s reply … (scroll to the bottom of the template posted above).

Thanks @dproffer and @123 for taking the time to look at my question. clearly you’ve given it some thought.

I initially thought this was going to be more complex, so came back to after a psyching myself up for a couple of days.

After a bit of work i have added some if … .elif statements in places without too much duplication.

The below is a copy of my ‘light’ action section which takes an MQTT message from Rhasspy voice assistant which has 2 slots and works out which light and whether you want to turn a light on, off, up or down. Thus you have 1 Automation for numerous lights and 4 actions.

I have made use of the new choose / default features for the actions so that i can have 2 actions otherwise ‘off’ breaks because it does not support ‘brightness’.

I guess what’s irritating is that if i was able to use an event trigger, the slot variables can be accessed directly without the if … elif test.

hopefully this will help someone.

choose:
  - conditions:
      - condition: or
        conditions:
          - condition: template
            value_template: >-
              {% if trigger.payload_json.slots[0].value.value == "off" %}True{%
              else %}False{% endif %}
          - condition: or
            conditions:
              - condition: template
                value_template: >-
                  {% if trigger.payload_json.slots[1].value.value == "off"
                  %}True{% else %}False{% endif %}
    sequence:
      - data_template:
          entity_id: |
            {% if trigger.payload_json.slots[0].slotName == "light_name" %}
              {% set the_light = "light." + trigger.payload_json.slots[0].value.value + '_light' %}
            {% else %}
              {% set the_light = "light." + trigger.payload_json.slots[1].value.value + '_light' %}
            {% endif %}
            {{ the_light }}
        service_template: light.turn_off
default:
  - data_template:
      brightness: >
        {% if trigger.payload_json.slots[0].slotName == "light_name" %}
          {% set vlightname = trigger.payload_json.slots[0].value.value %}
          {% set vlightstate = trigger.payload_json.slots[1].value.value %}
        {% else %}
          {% set vlightname = trigger.payload_json.slots[1].value.value %}
          {% set vlightstate = trigger.payload_json.slots[0].value.value %}
        {% endif %} {% set the_brightness = state_attr('light.' + vlightname +
        '_light', 'brightness') | int %}
          {% if vlightstate == "up" %}
            {% set the_brightness = the_brightness + 65 %}
          {% elif vlightstate == "down" %}
            {% set the_brightness = the_brightness - 65 %}
          {% elif vlightstate == "on" %}
            {% set the_brightness = 150 %} 
          {% else %}
            {% set the_brightness = 50 %} 
          {% endif %}
        {{ the_brightness }}
      entity_id: |
        {% if trigger.payload_json.slots[0].slotName == "light_name" %}
          {% set the_light = "light." + trigger.payload_json.slots[0].value.value + '_light' %}
        {% else %}
          {% set the_light = "light." + trigger.payload_json.slots[1].value.value + '_light' %}
        {% endif %}
        {{ the_light }}
    service_template: light.turn_on

Welcome to the ‘art and science’ of templates and jinga2! Good hunting :slight_smile:

Your example can be reduced to this with no loss of legibility (arguably, it’s easier to follow):

choose:
  - conditions:
      - condition: template
        value_template: >
          {{ trigger.payload_json.slots[0].value.value == "off" or
             trigger.payload_json.slots[1].value.value == "off" }}
    sequence:
      - service_template: light.turn_off
        data_template:
          entity_id: >
            {% set i = 0 if trigger.payload_json.slots[0].slotName == "light_name" else 1 %}
            light.{{trigger.payload_json.slots[i].value.value}}_light
default:
  - service_template: light.turn_on
    data_template:
      entity_id: >
        {% set i = 0 if trigger.payload_json.slots[0].slotName == "light_name" else 1 %}
        light.{{trigger.payload_json.slots[i].value.value}}_light
      brightness: >
        {% set i = 0 if trigger.payload_json.slots[0].slotName == 'light_name' else 1 %}
        {% set l = 'light.' ~ trigger.payload_json.slots[i].value.value ~ '_light' %}
        {% set s = trigger.payload_json.slots[(i - 1)|abs].value.value %}
        {% set b = state_attr(l, 'brightness') | int %}
        {% set map = {'up': b+65, 'down': b-65, 'on': 150} %}
        {{ map[s] if s in map.keys() else 50 }}

Question for you: in the portion that turns on the light, it allows the received command to be “down”. If the light is currently off, it has no brightness value. The template converts that to 0. However, it then proceeds to subtract 65 from 0 and submits a brightness of -65 to the service call (an invalid value). Are you aware of that or is there no possibility of receiving “down” when the light is off?

Let me know if you need a way of ensuring the calculated brightness is always between 0 and 255.


EDIT

Correction. Added missing if in brightness template.

thanks again @123 !

i have been using your streamlined version for a couple of days and works well. I have used maps before but it was a while ago. maybe when learning some java.

It had occurred to me about the out - of - range values. I had planned on adding those once the code was more or less where i want it. Using it, if the calculated value drops below 0 then the lamp turns off. this is o.k, but would probably be better if it stayed on at a low value of say 50.

I could put in some simple if… statements, but if you have an idea about something a bit neater i would be very interested to see your idea should you have time.

I did notice a small error in the code you posted last time. an ‘if’ was missing in the line below brightness: >. ie {% set i = 0 trigger.payload should be {% set i = 0 if trigger.payload

so i have re - posted

choose:
  - conditions:
      - condition: template
        value_template: >
          {{ trigger.payload_json.slots[0].value.value == "off" or
             trigger.payload_json.slots[1].value.value == "off" }}
    sequence:
      - service_template: light.turn_off
        data_template:
          entity_id: >
            {% set i = 0 if trigger.payload_json.slots[0].slotName == "light_name" else 1 %}
            light.{{trigger.payload_json.slots[i].value.value}}_light
default:
  - service_template: light.turn_on
    data_template:
      entity_id: >
        {% set i = 0 if trigger.payload_json.slots[0].slotName == "light_name" else 1 %}
        light.{{trigger.payload_json.slots[i].value.value}}_light
      brightness: >
        {% set i = 0 if trigger.payload_json.slots[0].slotName == 'light_name' else 1 %}
        {% set l = 'light.' ~ trigger.payload_json.slots[i].value.value ~ '_light' %}
        {% set s = trigger.payload_json.slots[(i - 1)|abs].value.value %}
        {% set b = state_attr(l, 'brightness') | int %}
        {% set map = {'up': b+65, 'down': b-65, 'on': 150} %}
        {{ map[s] if s in map.keys() else 50 }}

@123
indeed that is a bit neater! it’s quite a nice way of setting a range. At the moment i have used it to set the lower value to 25.
i replaced the last line (Map statement) of code under 'brightness with these 2 lines, which keeps the brightness between 25 and 255.

{% set b2 = map[s] if s in map.keys() else 50 %} 
{{ ([25, b2, 255]|sort)[1] }}

for the sake of completeness here is what i ended up with.

Thanks again.

choose:
  - conditions:
      - condition: template
        value_template: >
          {{ trigger.payload_json.slots[0].value.value == "off" or
             trigger.payload_json.slots[1].value.value == "off" }}
    sequence:
      - service_template: light.turn_off
        data_template:
          entity_id: >
            {% set i = 0 if trigger.payload_json.slots[0].slotName == "light_name" else 1 %}
            light.{{trigger.payload_json.slots[i].value.value}}_light
default:
  - service_template: light.turn_on
    data_template:
      entity_id: >
        {% set i = 0 if trigger.payload_json.slots[0].slotName == "light_name" else 1 %}
        light.{{trigger.payload_json.slots[i].value.value}}_light
      brightness: >
        {% set i = 0 if trigger.payload_json.slots[0].slotName == 'light_name' else 1 %}
        {% set l = 'light.' ~ trigger.payload_json.slots[i].value.value ~ '_light' %}
        {% set s = trigger.payload_json.slots[(i - 1)|abs].value.value %}
        {% set b = state_attr(l, 'brightness') | int %}
        {% set map = {'up': b+65, 'down': b-65, 'on': 150} %}
        {% set b2 = map[s] if s in map.keys() else 50 %}
        {{ ([25, b2, 255]|sort)[1] }}

You two exemplify my statement about Jinja2 artists/scientists. Thank you for the lessons, well done!