Dictionary not able to extract property

I’ve created several calendars to track the Garbage Collection schedules. As I want to display in a dashboard an image and a text with the next garbage to be collected.
The input is the next event for all the mentioned calendars which is generated by the macro how_long_till_next_event, as follows:

[
  {
    "calendar": "waste",
    "image": "black-container.jpg",
    "days": 5,
    "days_as_str": "in 5 days"
  },
  {
    "calendar": "plastic",
    "image": "yellow-container.jpg",
    "days": 19,
    "days_as_str": "in 19 days"
  },
  {
    "calendar": "paper",
    "image": "blue-container.jpg",
    "days": 26,
    "days_as_str": "in 26 days"
  }
]

The waste_management macro outputs a dictionary with the next garbage type to be collected (waste, plastic, paper)

{
  "calendar": "waste",
  "image": "black-container.jpg",
  "days": 5,
  "days_as_str": "in 5 days"
}

in the ideal world I would do:

{%set var = waste_management() %}
{{var.image}}

But when doing so I’m getting: str object' has no attribute 'image and don’t understand why; I’ve tried AI, converting from and to JSON and several other things and such a triviality is messing with my brain. Any help and simplification would be highly appreciated!

I’m leaving here the whole snippet for reference:

{%- macro format_time(in_how_many_days) -%}
  {%- if in_how_many_days == "0" -%}
  {% set time = 'Today!!' %}
  {%- elif in_how_many_days == "1" -%}
  {% set time = 'Tomorrow!' %}
  {%- else -%}
  {% set time = 'in ' ~ in_how_many_days ~ ' days' %}
  {%- endif -%}
  {{ time }}
{%- endmacro -%}

{%- macro how_long_till_next_event(calendar) -%}
  {%- set days = ((state_attr(calendar, 'start_time') | as_timestamp - today_at('00:00') | as_timestamp) / 86400 ) | int -%}
  {%- set calendar_name = calendar.replace('calendar.','') -%}
  {%- set image = calendar_name.replace('waste','black-container.jpg').replace('plastic','yellow-container.jpg').replace('paper','blue-container.jpg') -%}
  { 
    "calendar": {{ calendar_name | tojson }},
    "image": {{ image | tojson }},
    "days": {{ days | tojson }},
    "days_as_str": {{ format_time(days) | tojson }}
  }
{%- endmacro -%}

{%- macro get_garbage_calendars() -%}
[
  {{ how_long_till_next_event('calendar.waste') }},
  {{ how_long_till_next_event('calendar.plastic') }},
  {{ how_long_till_next_event('calendar.paper') }}
] 
{%- endmacro -%}

{% macro min_days_object(data) %}
  {%- set min_days_item = data | min(attribute='days') %}
  {{ min_days_item }}
{%- endmacro %}

{% macro waste_management() %}
{%- set sensor_array = get_garbage_calendars() -%}
{# {{sensor_array}} #}
{{ min_days_object(sensor_array | from_json) }}
{%- endmacro %}

{%set var = waste_management() %}

{# WORKS #}
{{var}}
{# DOESN'T WORK #}
{{var.image}}

It’s likely that var is a string that looks like JSON, because macro outputs are always strings, and the template editor is trying to be helpful converting it for you. Try:

{{ (var|from_json)['image'] }}

And if that doesn’t work, try adding a |to_json at the end of the output in waste_management() to force it to a JSON string:

{{ (min_days_object(sensor_array | from_json))|to_json }}

Play with this in the template editor to see the complexities:

{% macro number(x) -%}
{{ {"number": x}|to_json -}}
{% endmacro %}
{{ (number(2)|from_json)['number'] }}
1 Like

Thanks Troon For the suggestion.

Out of curiosity I’ve tried:

{{ (var|to_json) }}

and got an output:

"\n\n  {'calendar': 'waste', 'image': 'black-container.jpg', 'days': 5, 'days_as_str': 'in 5 days'}"

When I tried your suggestion:

{{ (var|from_json)['image'] }}

I got:

JSONDecodeError: unexpected character: line 3 column 4 (char 5)

See my edit about |to_json-ing the output from waste_management().

When I try that:

{% macro waste_management() %}
{%- set sensor_array = get_garbage_calendars() -%}
{{ (min_days_object(sensor_array | from_json) | to_json) }}
{%- endmacro %}

{{(waste_management() | from_json)}}

I get:

{
  "calendar": "waste",
  "image": "black-container.jpg",
  "days": 5,
  "days_as_str": "in 5 days"
}

image

And when I do:

{{(waste_management() | from_json)['image']}}

I’m getting again:

str object' has no attribute 'image'

You didn’t try “that” exactly. Look where the brackets are.

me:
{{ (min_days_object(sensor_array | from_json))|to_json }}
you:
{{ (min_days_object(sensor_array | from_json) | to_json) }}

I think mine will work:

When I do:

{{(((waste_management() | from_json ) | to_json).replace('\"','').replace('\\n  ',''))}}

Returns a dictionary…

Result type: dict

{
  "calendar": "waste",
  "image": "black-container.jpg",
  "days": 5,
  "days_as_str": "in 5 days"
}

To a dictionary, you can do: .image or ['image'] AFIK, but when I do that…

{{(((waste_management() | from_json ) | to_json).replace('\"','').replace('\\n  ',''))['image']}}

I’m getting:

'str object' has no attribute 'image'
Result type: string

Question is: Why? :slight_smile:

As I said earlier, the template editor does a lot of unhelpful / helpful conversions for you that makes you think you’re getting a dict when it’s actually a string.

You’re getting wrapped up in JSON mire here. Can you try my suggestion into the waste_management() macro:

{{ (min_days_object(sensor_array | from_json)) | to_json }}

then try:

{{ (waste_management() | from_json)['image'] }}

Tried it…

{% macro waste_management() %}
{%- set sensor_array = get_garbage_calendars() -%}
{{ (min_days_object(sensor_array | from_json)) | to_json }}
{%- endmacro %}



{% macro waste_management() %}
{%- set sensor_array = get_garbage_calendars() -%}
{{ (min_days_object(sensor_array | from_json)) | to_json }}
{%- endmacro %}

{{ (waste_management()) }}

{{ (waste_management() | from_json)['image'] }}

Same outcome…

'str object' has no attribute 'image'
Result type: string

"\n  {'calendar': 'waste', 'image': 'black-container.jpg', 'days': 5, 'days_as_str': 'in 5 days'}"

Is there a particular reason why your macro generates values using tojson like this:

  { 
    "calendar": {{ calendar_name | tojson }},
    "image": {{ image | tojson }},
    "days": {{ days | tojson }},
    "days_as_str": {{ format_time(days) | tojson }}
  }

instead of this?

  { 
    "calendar": "{{ calendar_name }}",
    "image": "{{ image }}",
    "days": {{ days }},
    "days_as_str": "{{ format_time(days) }}"
  }

No particular reason, I even tried…

{{ {'calendar': calendar_name, 'image': image, 'days': days, 'days_as_str': format_time(days) } | to_json }}

still same outcome when trying to get a single property

Try

{{ ((waste_management() | from_json)|from_json)['image'] }}

… because it looks like you’re getting a JSON string back even after the first conversion.

{{ ((waste_management() | from_json)|from_json)['image'] }}

outputs

JSONDecodeError: unexpected character: line 2 column 4 (char 4)

only

{{ waste_management() }}

outputs as Result type: dict

{
  "calendar": "waste",
  "image": "black-container.jpg",
  "days": 5,
  "days_as_str": "in 5 days"
}

if I add anything else in the template editor…

Something

{{ waste_management() }}

Result is string and everything now is a string

Result type: string

Something


  {'calendar': 'waste', 'image': 'black-container.jpg', 'days': 5, 'days_as_str': 'in 5 days'}

Totally not proud of the code style, but works…

{% macro waste_management() %}
{% set next_waste_days = how_long_till_next_event('calendar.waste') | int %}
{% set next_plastic_days = how_long_till_next_event('calendar.plastic') | int %}
{% set next_paper_days = how_long_till_next_event('calendar.paper') | int %}

{% set next_collection = 'Waste' %}
{% set image = 'black-container.jpg' %}
{% set next_collection_date = format_time(next_waste_days) %}
{% set next_collection_days = next_waste_days %}

{%- if next_plastic_days < next_waste_days -%}
{% set next_collection = 'Plastic' %}
{% set image = 'yellow-container.jpg' %}
{% set next_collection_date = format_time(next_plastic_days) %}
{% set next_collection_days = next_plastic_days %}
{%- endif -%}

{%- if next_paper_days < next_waste_days -%}
{% set next_collection = 'Paper' %}
{% set image = 'blue-container.jpg' %}
{% set next_collection_date = format_time(next_paper_days) %}
{% set next_collection_days = next_paper_days %}
{%- endif -%}

{{ {'next_collection': next_collection, 'next_collection_image': image, 'next_collection_date': next_collection_date, 'next_collection_days': next_collection_days } | to_json }}
{%- endmacro -%}

{%- macro format_time(in_how_many_days) -%}
  {%- if in_how_many_days == "0" -%}
  {% set time = 'Today!!' %}
  {%- elif in_how_many_days == "1" -%}
  {% set time = 'Tomorrow!' %}
  {%- else -%}
  {% set time = 'in ' ~ in_how_many_days ~ ' days' %}
  {%- endif -%}
  {{ time }}
{%- endmacro -%}

{%- macro how_long_till_next_event(calendar) -%}
{{ ((state_attr(calendar, 'start_time') | as_timestamp - today_at('00:00') | as_timestamp) / 86400 ) | int }}
{%- endmacro -%}
1 Like

The output of a macro is always a string, so | from_json is indeed needed to convert it to a dictionary.

Besides that, they often add whitespace. To avoid that, use {%- and -%} but also {{- and -}} everywhere in the macro.

It’s likely that var is a string that looks like JSON, because macro outputs are always strings, and the template editor is trying to be helpful converting it for you.

It’s not trying to be helpful, it is giving the same result as it would give when used in a automation/script/template sensor (attribute).
The output of a Jinja template (like a macro) is always a string. HA wil try to find a matching native type and convert it to that type

That’s why something like this will work without converting the template output to a number

service: input_number.set_value
target:
  entity_id: input_number.foo
data:
  value: "{{ states('sensor.bar') }}"
1 Like