import json
from typing import Any, Union


class JsonBase:

    def __init__(self, *, fp: str, indent: int) -> None:
        self._fp = fp
        self._indent_size = indent

    @property
    def data(self) -> dict:
        """Returns the all data of the json file."""
        with open(self._fp) as f:
            return json.load(f)

    def dump(self, data: dict) -> None:
        """Dumps the data to the json file."""
        with open(self._fp, mode="w") as f:
            json.dump(data, f, indent=self._indent_size)

    def validate(self) -> None:
        """Validates the json file."""
        try:
            with open(self._fp, mode="r") as f:
                data = json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            with open(self._fp, mode="w") as f:
                json.dump({}, f)
                return
        if not isinstance(data, dict):
            with open(self._fp, mode="w") as f:
                json.dump({}, f)


class JsonWrapper(JsonBase):

    def __init__(self, fp: str, *, indent: int = 4) -> None:
        super().__init__(fp=fp, indent=indent)
        self.validate()

    def get(self, path: str = "", default: Any = None) -> Union[str, int, bool, dict, list, None]:
        """Returns the value of the key in given path. If the key is not found in the path, returns the default value.

        Args:
            path (str): The path to the key. If the path is not given, returns the whole json as a dict.
            default (Any): The default value.

        Returns:
            Union[str, int, bool, dict, list, None]: The value of the key in given path.
        """
        self.validate()
        path, data = [p for p in path.split(".") if p != ""], self.data
        for p in path:
            if p not in data:
                return default
            data = data[p]
        return data

    def set(self, path: str, value: Union[str, int, bool, dict, list, None], *, 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.

        Args:
            path (str): The path to set the value in.
            value (Union[str, int, bool, dict, list, None]): The value to set.
            force (bool): 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.
            TypeError: If the path is blocked by a non-dictionary value and force is False.
        """
        self.validate()
        path, data = [p for p in path.split(".") if p != ""], self.data
        reference = data
        if not path:
            raise ValueError("Path is empty.")
        for p in path[:-1]:
            if p not in data.keys():
                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(reference)

    def rem(self, path: str = "") -> bool:
        """Removes the key in given path.

        Args:
            path (str): 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.
        """
        self.validate()
        path, data = [p for p in path.split(".") if p != ""], self.data
        reference = data
        if not path:
            if data:
                self.dump({})
                return True
            return False
        for p in path[:-1]:
            if p not in data.keys():
                return False
            elif not isinstance(data[p], dict):
                return False
            data = data[p]
        if path[-1] not in data.keys():
            return False
        del data[path[-1]]
        self.dump(reference)
        return True

    def __contains__(self, key: str) -> bool:
        self.validate()
        return key in self.data

    def __delitem__(self, key: str) -> None:
        self.validate()
        data = self.data
        del data[key]
        self.dump(data)

    def __eq__(self, other: Any) -> bool:
        return self.data == other

    def __getitem__(self, key: str) -> Union[str, int, bool, dict, list, None]:
        self.validate()
        return self.data[key]

    def __ge__(self, other: Any) -> bool:
        self.validate()
        return self.data >= other

    def __gt__(self, other: Any) -> bool:
        self.validate()
        return self.data > other

    def __ior__(self, other) -> None:
        self.validate()
        data = self.data
        data.update(other)
        self.dump(data)

    def __iter__(self):
        self.validate()
        return iter(self.data)

    def __len__(self) -> int:
        self.validate()
        return len(self.data)

    def __le__(self, other: Any) -> bool:
        self.validate()
        return self.data <= other

    def __lt__(self, other: Any) -> bool:
        self.validate()
        return self.data < other

    def __ne__(self, other: Any) -> bool:
        self.validate()
        return self.data != other

    def __or__(self, other: Any) -> Any:
        self.validate()
        return self.data | other

    def __repr__(self) -> str:
        self.validate()
        return repr(self.data)

    def __reversed__(self):
        self.validate()
        return reversed(self.data)

    def __ror__(self, other: Any) -> bool:
        self.validate()
        return other | self.data

    def __setitem__(self, key: str, value: Union[str, int, bool, dict, list, None]) -> None:
        self.validate()
        data = self.data
        data[key] = value
        self.dump(data)

    def __sizeof__(self) -> bytes:
        self.validate()
        return bytes(self.data)

import json
from typing import Any, Union


class JsonBase:

    def __init__(self, *, fp: str, indent: int) -> None:
        self._fp = fp
        self._indent_size = indent

    @property
    def data(self) -> dict:
        """Returns the all data of the json file."""
        with open(self._fp) as f:
            return json.load(f)

    def dump(self, data: dict) -> None:
        """Dumps the data to the json file."""
        with open(self._fp, mode="w") as f:
            json.dump(data, f, indent=self._indent_size)

    def validate(self) -> None:
        """Validates the json file."""
        try:
            with open(self._fp, mode="r") as f:
                data = json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            with open(self._fp, mode="w") as f:
                json.dump({}, f)
                return
        if not isinstance(data, dict):
            with open(self._fp, mode="w") as f:
                json.dump({}, f)


class JsonWrapper(JsonBase):

    def __init__(self, fp: str, *, indent: int = 4) -> None:
        super().__init__(fp=fp, indent=indent)
        self.validate()

    def get(self, path: str = "", default: Any = None) -> Union[str, int, bool, dict, list, None]:
        """Returns the value of the key in given path. If the key is not found in the path, returns the default value.

        Args:
            path (str): The path to the key. If the path is not given, returns the whole json as a dict.
            default (Any): The default value.

        Returns:
            Union[str, int, bool, dict, list, None]: The value of the key in given path.
        """
        self.validate()
        path, data = [p for p in path.split(".") if p != ""], self.data
        for p in path:
            if p not in data:
                return default
            data = data[p]
        return data

    def set(self, path: str, value: Union[str, int, bool, dict, list, None], *, 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.

        Args:
            path (str): The path to set the value in.
            value (Union[str, int, bool, dict, list, None]): The value to set.
            force (bool): 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.
            TypeError: If the path is blocked by a non-dictionary value and force is False.
        """
        self.validate()
        path, data = [p for p in path.split(".") if p != ""], self.data
        reference = data
        if not path:
            raise ValueError("Path is empty.")
        for p in path[:-1]:
            if p not in data.keys():
                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(reference)

    def rem(self, path: str = "") -> bool:
        """Removes the key in given path.

        Args:
            path (str): 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.
        """
        self.validate()
        path, data = [p for p in path.split(".") if p != ""], self.data
        reference = data
        if not path:
            if data:
                self.dump({})
                return True
            return False
        for p in path[:-1]:
            if p not in data.keys():
                return False
            elif not isinstance(data[p], dict):
                return False
            data = data[p]
        if path[-1] not in data.keys():
            return False
        del data[path[-1]]
        self.dump(reference)
        return True

    def __contains__(self, key: str) -> bool:
        self.validate()
        return key in self.data

    def __delitem__(self, key: str) -> None:
        self.validate()
        data = self.data
        del data[key]
        self.dump(data)

    def __eq__(self, other: Any) -> bool:
        return self.data == other

    def __getitem__(self, key: str) -> Union[str, int, bool, dict, list, None]:
        self.validate()
        return self.data[key]

    def __ge__(self, other: Any) -> bool:
        self.validate()
        return self.data >= other

    def __gt__(self, other: Any) -> bool:
        self.validate()
        return self.data > other

    def __ior__(self, other) -> None:
        self.validate()
        data = self.data
        data.update(other)
        self.dump(data)

    def __iter__(self):
        self.validate()
        return iter(self.data)

    def __len__(self) -> int:
        self.validate()
        return len(self.data)

    def __le__(self, other: Any) -> bool:
        self.validate()
        return self.data <= other

    def __lt__(self, other: Any) -> bool:
        self.validate()
        return self.data < other

    def __ne__(self, other: Any) -> bool:
        self.validate()
        return self.data != other

    def __or__(self, other: Any) -> Any:
        self.validate()
        return self.data | other

    def __repr__(self) -> str:
        self.validate()
        return repr(self.data)

    def __reversed__(self):
        self.validate()
        return reversed(self.data)

    def __ror__(self, other: Any) -> bool:
        self.validate()
        return other | self.data

    def __setitem__(self, key: str, value: Union[str, int, bool, dict, list, None]) -> None:
        self.validate()
        data = self.data
        data[key] = value
        self.dump(data)

    def __sizeof__(self) -> bytes:
        self.validate()
        return bytes(self.data)

Returns the all data of the json file. Dumps the data to the json file. Validates the json file. Returns the value of the key in given path. If the key is not found in the path, returns the default value.          Args:             path (str): The path to the key. If the path is not given, returns the whole json as a dict.             default (Any): The default value.          Returns:             Union[str, int, bool, dict, list, None]: The value of the key in given path. 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.          Args:             path (str): The path to set the value in.             value (Union[str, int, bool, dict, list, None]): The value to set.             force (bool): 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.             TypeError: If the path is blocked by a non-dictionary value and force is False. Removes the key in given path.          Args:             path (str): 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.