1 """Helpers to help with encoding Home Assistant objects in JSON."""
3 from collections
import deque
4 from collections.abc
import Callable
6 from functools
import partial
9 from pathlib
import Path
10 from typing
import TYPE_CHECKING, Any, Final
16 JSON_DECODE_EXCEPTIONS
as _JSON_DECODE_EXCEPTIONS,
17 JSON_ENCODE_EXCEPTIONS
as _JSON_ENCODE_EXCEPTIONS,
19 format_unserializable_data,
20 json_loads
as _json_loads,
23 from .deprecation
import (
25 all_with_deprecated_constants,
26 check_if_deprecated_constant,
28 dir_with_deprecated_constants,
32 _JSON_DECODE_EXCEPTIONS,
"homeassistant.util.json.JSON_DECODE_EXCEPTIONS",
"2025.8"
35 _JSON_ENCODE_EXCEPTIONS,
"homeassistant.util.json.JSON_ENCODE_EXCEPTIONS",
"2025.8"
37 json_loads = deprecated_function(
38 "homeassistant.util.json.json_loads", breaks_in_ha_version=
"2025.8"
42 __getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
44 dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
49 _LOGGER = logging.getLogger(__name__)
53 """JSONEncoder that supports Home Assistant objects."""
56 """Convert Home Assistant objects.
58 Hand other objects to the original method.
60 if isinstance(o, datetime.datetime):
62 if isinstance(o, set):
64 if hasattr(o,
"as_dict"):
67 return json.JSONEncoder.default(self, o)
71 """Convert Home Assistant objects.
73 Hand other objects to the original method.
75 if hasattr(obj,
"json_fragment"):
76 return obj.json_fragment
77 if isinstance(obj, (set, tuple)):
79 if isinstance(obj, float):
81 if hasattr(obj,
"as_dict"):
83 if isinstance(obj, Path):
85 if isinstance(obj, datetime.datetime):
86 return obj.isoformat()
93 """Dump json bytes."""
97 orjson.dumps, option=orjson.OPT_NON_STR_KEYS, default=json_encoder_default
99 """Dump json bytes."""
103 """JSONEncoder that supports Home Assistant objects and falls back to repr(o)."""
106 """Convert certain objects.
108 Fall back to repr(o).
110 if isinstance(o, datetime.timedelta):
111 return {
"__type":
str(type(o)),
"total_seconds": o.total_seconds()}
112 if isinstance(o, datetime.datetime):
114 if isinstance(o, (datetime.date, datetime.time)):
115 return {
"__type":
str(type(o)),
"isoformat": o.isoformat()}
119 return {
"__type":
str(type(o)),
"repr": repr(o)}
123 """Strip NUL from an object."""
124 if isinstance(obj, str):
125 return obj.split(
"\0", 1)[0]
126 if isinstance(obj, dict):
127 return {key:
_strip_null(o)
for key, o
in obj.items()}
128 if isinstance(obj, list):
134 """Dump json bytes after terminating strings at the first NUL."""
138 if b
"\\u0000" not in result:
146 json_fragment = orjson.Fragment
150 r"""Dump json string.
152 orjson supports serializing dataclasses natively which
153 eliminates the need to implement as_dict in many places
154 when the data is already in a dataclass. This works
155 well as long as all the data in the dataclass can also
158 If it turns out to be a problem we can disable this
159 with option \|= orjson.OPT_PASSTHROUGH_DATACLASS and it
160 will fallback to as_dict
165 json_bytes_sorted = partial(
167 option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SORT_KEYS,
168 default=json_encoder_default,
170 """Dump json bytes with keys sorted."""
174 """Dump json string with keys sorted."""
178 JSON_DUMP: Final = json_dumps
182 """JSON encoder that uses orjson with hass defaults and returns a str."""
187 """JSON encoder that uses orjson with hass defaults and returns bytes."""
190 option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS,
191 default=json_encoder_default,
198 private: bool =
False,
201 atomic_writes: bool =
False,
203 """Save JSON data to a file."""
204 dump: Callable[[Any], Any]
209 if encoder
and encoder
is not JSONEncoder:
214 json_data: str | bytes = json.dumps(data, indent=2, cls=encoder)
217 dump = _orjson_default_encoder
219 except TypeError
as error:
223 msg = f
"Failed to serialize to JSON: {filename}. Bad data at {formatted_data}"
225 raise SerializationError(msg)
from error
227 method = write_utf8_file_atomic
if atomic_writes
else write_utf8_file
228 method(filename, json_data, private, mode=mode)
232 bad_data: Any, *, dump: Callable[[Any], str] = json.dumps
234 """Find the paths to unserializable data.
236 This method is slow! Only use for error handling.
243 to_process = deque([(bad_data,
"$")])
247 obj, obj_path = to_process.popleft()
252 except (ValueError, TypeError):
257 if hasattr(obj,
"as_dict"):
258 desc = obj.__class__.__name__
259 if isinstance(obj, State):
260 desc += f
": {obj.entity_id}"
261 elif isinstance(obj, Event):
262 desc += f
": {obj.event_type}"
264 obj_path += f
"({desc})"
267 if isinstance(obj, dict):
268 for key, value
in obj.items():
273 invalid[f
"{obj_path}<key: {key}>"] = key
276 to_process.append((value, f
"{obj_path}.{key}"))
277 elif isinstance(obj, list):
278 for idx, value
in enumerate(obj):
279 to_process.append((value, f
"{obj_path}[{idx}]"))
281 invalid[obj_path] = obj
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
bytes _orjson_bytes_default_encoder(Any data)
str _orjson_default_encoder(Any data)
bytes json_bytes_strip_null(Any data)
str json_dumps_sorted(Any data)
Any json_encoder_default(Any obj)
dict[str, Any] find_paths_unserializable_data(Any bad_data, *Callable[[Any], str] dump=json.dumps)
None save_json(str filename, list|dict data, bool private=False, *type[json.JSONEncoder]|None encoder=None, bool atomic_writes=False)
str format_unserializable_data(dict[str, Any] data)
str dump(dict|list _dict)