Skip to content
30 changes: 29 additions & 1 deletion homeassistant/helpers/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from jinja2.sandbox import ImmutableSandboxedEnvironment
from jinja2.utils import Namespace
from lru import LRU # pylint: disable=no-name-in-module
import orjson
import voluptuous as vol

from homeassistant.const import (
Expand Down Expand Up @@ -150,6 +151,10 @@
)
ENTITY_COUNT_GROWTH_FACTOR = 1.2

ORJSON_PASSTHROUGH_OPTIONS = (
orjson.OPT_PASSTHROUGH_DATACLASS | orjson.OPT_PASSTHROUGH_DATETIME
)


def _template_state_no_collect(hass: HomeAssistant, state: State) -> TemplateState:
"""Return a TemplateState for a state without collecting."""
Expand Down Expand Up @@ -2031,9 +2036,31 @@ def from_json(value):

def to_json(value, ensure_ascii=True):
"""Convert an object to a JSON string."""
_LOGGER.warning("Template warning: 'to_json' is deprecated, use 'as_json' instead")
Comment thread
depoll marked this conversation as resolved.
Outdated
return json.dumps(value, ensure_ascii=ensure_ascii)


def _as_json_default(obj):
Comment thread
depoll marked this conversation as resolved.
Outdated
"""Disable custom types in json serialization."""
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")


def as_json(value, pretty_print=False, sort_keys=False):
Comment thread
depoll marked this conversation as resolved.
Outdated
"""Convert an object to a JSON string."""

option = (
ORJSON_PASSTHROUGH_OPTIONS
| (orjson.OPT_INDENT_2 if pretty_print else 0)
| (orjson.OPT_SORT_KEYS if sort_keys else 0)
)

return orjson.dumps(
value,
option=option,
default=_as_json_default,
).decode("utf-8")


@pass_context
def random_every_time(context, values):
"""Choose a random value.
Expand Down Expand Up @@ -2259,7 +2286,8 @@ def __init__(self, hass, limited=False, strict=False):
self.filters["timestamp_custom"] = timestamp_custom
self.filters["timestamp_local"] = timestamp_local
self.filters["timestamp_utc"] = timestamp_utc
self.filters["to_json"] = to_json
self.filters["to_json"] = to_json # deprecated
Comment thread
depoll marked this conversation as resolved.
Outdated
self.filters["as_json"] = as_json
self.filters["from_json"] = from_json
self.filters["is_defined"] = fail_when_undefined
self.filters["average"] = average
Expand Down
30 changes: 30 additions & 0 deletions tests/helpers/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from unittest.mock import patch

from freezegun import freeze_time
import orjson
import pytest
import voluptuous as vol

Expand Down Expand Up @@ -1048,6 +1049,35 @@ def test_to_json(hass: HomeAssistant) -> None:
assert actual_result == expected_result


def test_as_json(hass: HomeAssistant) -> None:
"""Test the object to JSON string filter."""

# Note that we're not testing the actua orjson.dumps methods,
Comment thread
depoll marked this conversation as resolved.
Outdated
# only the filters, so we don't need to be exhaustive with our sample JSON.
expected_result = {"Foo": "Bar"}
actual_result = template.Template(
"{{ {'Foo': 'Bar'} | as_json }}", hass
).async_render()
assert actual_result == expected_result

expected_result = orjson.dumps({"Foo": "Bar"}, option=orjson.OPT_INDENT_2).decode()
actual_result = template.Template(
"{{ {'Foo': 'Bar'} | as_json(pretty_print=True) }}", hass
).async_render(parse_result=False)
assert actual_result == expected_result

expected_result = orjson.dumps(
{"Z": 26, "A": 1, "M": 13}, option=orjson.OPT_SORT_KEYS
).decode()
actual_result = template.Template(
"{{ {'Z': 26, 'A': 1, 'M': 13} | as_json(sort_keys=True) }}", hass
).async_render(parse_result=False)
assert actual_result == expected_result

with pytest.raises(TemplateError):
template.Template("{{ {'Foo': now()} | as_json }}", hass).async_render()


def test_to_json_string(hass: HomeAssistant) -> None:
"""Test the object to JSON string filter."""

Expand Down