1
Fork 0
mirror of https://github.com/RGBCube/JsonWrapper synced 2025-07-27 03:37:44 +00:00
JsonWrapper/json_wrapper.py
2023-04-28 22:45:48 +03:00

184 lines
5.3 KiB
Python

import json
from io import TextIOWrapper
from typing import Any
JsonType = str | int | bool | dict | list | None
class JsonFile:
def __init__(self, path: str, **kwargs: Any) -> None:
self.path = path
self._json_kwargs = kwargs
def open(self, mode: str = "r", /) -> TextIOWrapper:
return open(self.path, mode)
def dump(self, data: dict, /) -> None:
"""Dumps the data to the JSON file."""
with self.open("w") as file:
json.dump(data, file, **self._json_kwargs)
def data(self) -> dict[Any, Any]:
"""Returns the all data in the JSON file."""
with self.open() as file:
return json.load(file, **self._json_kwargs)
def validate(self) -> dict:
"""Validates the JSON file contents and returns them."""
try:
data = self.data()
if not isinstance(data, dict):
data = {}
self.dump(data)
except (FileNotFoundError, json.JSONDecodeError):
data = {}
self.dump(data)
return data
class JsonWrapper(JsonFile):
def __init__(self, path: str, /, *, indent: int = 4) -> None:
super().__init__(path, indent=indent)
self.validate()
def get(self, path: str | list[str] = [], /, default: Any = None) -> JsonType:
"""
Returns the value of the key in given path. If the key is not found in the path, returns the default value.
For example, if `jw.get("foo.bar.baz")` is called and the JSON is `{"foo": {"bar": {"baz": 123}}}`, `123` will be returned.
Args:
path: The path to the key. If not given, returns the whole JSON.
default: The default value.
Returns:
The value of the key in given path.
"""
data = self.validate()
path = path.split(".") if isinstance(path, str) else path
for part in path:
if part not in data:
return default
data = data[p]
return data
def set(self, path: str | list[str], value: JsonType, /, *, force: bool = False) -> None:
"""Sets the path to the value. If the path is not found, creates the path.
If the path is blocked by a non-dictionary value, it will raise TypeError, to override this behavior, set force to True.
Note: Use `JsonWrapper.dump` if you want to set the whole JSON to a dict.
Args:
path: The path to set the value in.
value: The value to set.
force: If True, it overrides the path if the path is blocked by a non-dictionary value.
Raises:
ValueError: If the path is an empty string. See note.
TypeError: If the path is blocked by a non-dictionary value while force is False.
"""
data = self.validate()
data_root = data
path = path.split(".") if isinstance(path, str) else path
if not path:
raise ValueError("Path is empty.")
# All except the last.
for p in path[:-1]:
if p not in data:
data[p] = {}
elif not isinstance(data[p], dict):
if force:
data[p] = {}
else:
raise TypeError("Path is blocked by a non-dictionary value. Use force=True to override.")
data = data[p]
data[path[-1]] = value
self.dump(data_root)
def rem(self, path: str | list[str] = [], /) -> bool:
"""Removes the key in given path.
Args:
path: The path to the key. If the path is not given, nukes the whole json.
Returns:
bool: True if the key was removed, False if the key was not found.
"""
data = self.validate()
data_root = data
path = path.split(".") if isinstance(path, str) else path
if not path:
if data:
self.dump({})
return True
return False
# All except the last.
for part in path[:-1]:
if part not in data or not isinstance(data[part], dict):
return False
data = data[part]
if path[-1] not in data:
return False
del data[path[-1]]
self.dump(data_root)
return True
def __getitem__(self, key: str, /) -> JsonType:
return self.validate()[key]
def __setitem__(self, key: str, value: Union[str, int, bool, dict, list, None]) -> None:
data = self.validate()
data[key] = value
self.dump(data)
def __delitem__(self, key: str, /) -> None:
data = self.validate()
del data[key]
self.dump(data)
def __contains__(self, key: str, /) -> bool:
return key in self.validate()
def __repr__(self) -> str:
return repr(self.validate())
def __str__(self) -> str:
return str(self.validate())
def __reversed__(self):
return reversed(self.validate())
def __eq__(self, other: Any, /) -> bool:
return self.data == other
def __ior__(self, other: Any) -> bool:
data = self.validate()
data |= other
self.dump(data)
def __ror__(self, other: Any) -> bool:
return other | self.validate()
def __sizeof__(self) -> bytes:
return self.validate().__sizeof__()