Home Assistant Unofficial Reference 2024.12.1
json.py
Go to the documentation of this file.
1 """JSON utility functions."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from os import PathLike
7 from typing import Any
8 
9 import orjson
10 
11 from homeassistant.exceptions import HomeAssistantError
12 
13 _SENTINEL = object()
14 _LOGGER = logging.getLogger(__name__)
15 
16 type JsonValueType = (
17  dict[str, JsonValueType] | list[JsonValueType] | str | int | float | bool | None
18 )
19 """Any data that can be returned by the standard JSON deserializing process."""
20 type JsonArrayType = list[JsonValueType]
21 """List that can be returned by the standard JSON deserializing process."""
22 type JsonObjectType = dict[str, JsonValueType]
23 """Dictionary that can be returned by the standard JSON deserializing process."""
24 
25 JSON_ENCODE_EXCEPTIONS = (TypeError, ValueError)
26 JSON_DECODE_EXCEPTIONS = (orjson.JSONDecodeError,)
27 
28 
30  """Error serializing the data to JSON."""
31 
32 
33 def json_loads(obj: bytes | bytearray | memoryview | str, /) -> JsonValueType:
34  """Parse JSON data.
35 
36  This adds a workaround for orjson not handling subclasses of str,
37  https://github.com/ijl/orjson/issues/445.
38  """
39  # Avoid isinstance overhead for the common case
40  if type(obj) not in (bytes, bytearray, memoryview, str) and isinstance(obj, str):
41  return orjson.loads(str(obj)) # type:ignore[no-any-return]
42  return orjson.loads(obj) # type:ignore[no-any-return]
43 
44 
45 def json_loads_array(obj: bytes | bytearray | memoryview | str, /) -> JsonArrayType:
46  """Parse JSON data and ensure result is a list."""
47  value: JsonValueType = json_loads(obj)
48  # Avoid isinstance overhead as we are not interested in list subclasses
49  if type(value) is list: # noqa: E721
50  return value
51  raise ValueError(f"Expected JSON to be parsed as a list got {type(value)}")
52 
53 
54 def json_loads_object(obj: bytes | bytearray | memoryview | str, /) -> JsonObjectType:
55  """Parse JSON data and ensure result is a dictionary."""
56  value: JsonValueType = json_loads(obj)
57  # Avoid isinstance overhead as we are not interested in dict subclasses
58  if type(value) is dict: # noqa: E721
59  return value
60  raise ValueError(f"Expected JSON to be parsed as a dict got {type(value)}")
61 
62 
64  filename: str | PathLike[str],
65  default: JsonValueType = _SENTINEL, # type: ignore[assignment]
66 ) -> JsonValueType:
67  """Load JSON data from a file.
68 
69  Defaults to returning empty dict if file is not found.
70  """
71  try:
72  with open(filename, mode="rb") as fdesc:
73  return orjson.loads(fdesc.read()) # type: ignore[no-any-return]
74  except FileNotFoundError:
75  # This is not a fatal error
76  _LOGGER.debug("JSON file not found: %s", filename)
77  except JSON_DECODE_EXCEPTIONS as error:
78  _LOGGER.exception("Could not parse JSON content: %s", filename)
79  raise HomeAssistantError(f"Error while loading {filename}: {error}") from error
80  except OSError as error:
81  _LOGGER.exception("JSON file reading failed: %s", filename)
82  raise HomeAssistantError(f"Error while loading {filename}: {error}") from error
83  return {} if default is _SENTINEL else default
84 
85 
87  filename: str | PathLike[str],
88  default: JsonArrayType = _SENTINEL, # type: ignore[assignment]
89 ) -> JsonArrayType:
90  """Load JSON data from a file and return as list.
91 
92  Defaults to returning empty list if file is not found.
93  """
94  if default is _SENTINEL:
95  default = []
96  value: JsonValueType = load_json(filename, default=default)
97  # Avoid isinstance overhead as we are not interested in list subclasses
98  if type(value) is list: # noqa: E721
99  return value
100  _LOGGER.exception(
101  "Expected JSON to be parsed as a list got %s in: %s", {type(value)}, filename
102  )
103  raise HomeAssistantError(f"Expected JSON to be parsed as a list got {type(value)}")
104 
105 
107  filename: str | PathLike[str],
108  default: JsonObjectType = _SENTINEL, # type: ignore[assignment]
109 ) -> JsonObjectType:
110  """Load JSON data from a file and return as dict.
111 
112  Defaults to returning empty dict if file is not found.
113  """
114  if default is _SENTINEL:
115  default = {}
116  value: JsonValueType = load_json(filename, default=default)
117  # Avoid isinstance overhead as we are not interested in dict subclasses
118  if type(value) is dict: # noqa: E721
119  return value
120  _LOGGER.exception(
121  "Expected JSON to be parsed as a dict got %s in: %s", {type(value)}, filename
122  )
123  raise HomeAssistantError(f"Expected JSON to be parsed as a dict got {type(value)}")
124 
125 
126 def format_unserializable_data(data: dict[str, Any]) -> str:
127  """Format output of find_paths in a friendly way.
128 
129  Format is comma separated: <path>=<value>(<type>)
130  """
131  return ", ".join(f"{path}={value}({type(value)}" for path, value in data.items())
None open(self, **Any kwargs)
Definition: lock.py:86
JsonObjectType load_json_object(str|PathLike[str] filename, JsonObjectType default=_SENTINEL)
Definition: json.py:109
JsonArrayType load_json_array(str|PathLike[str] filename, JsonArrayType default=_SENTINEL)
Definition: json.py:89
JsonArrayType json_loads_array(bytes|bytearray|memoryview|str obj)
Definition: json.py:45
JsonObjectType json_loads_object(bytes|bytearray|memoryview|str obj)
Definition: json.py:54
JsonValueType json_loads(bytes|bytearray|memoryview|str obj)
Definition: json.py:33
JsonValueType load_json(str|PathLike[str] filename, JsonValueType default=_SENTINEL)
Definition: json.py:66
str format_unserializable_data(dict[str, Any] data)
Definition: json.py:126