"""JSONablize (:mod:`qurry.capsule.jsonablize`)"""
import os
from typing import Union, Any, Optional
from collections import OrderedDict
from collections.abc import Iterable, Hashable
import json
from pathlib import Path
from .utils import DEFAULT_ENCODING, DEFAULT_INDENT
[docs]
def value_parse(v: Any) -> Union[Iterable, str, int, float, bool, None]:
"""Make value JSON-allowable.
If a value is not allowed by :func:`~json.dumps`, then return its `str` representation.
Args:
v (Any): Value.
Returns:
A JSON-allowable value, which can be an iterable, str, int, float, bool or None.
"""
try:
json.dumps(v)
return v
except TypeError:
return str(v)
[docs]
def key_parse(k: Any) -> Union[str, int, float, bool, None]:
"""Make key JSON-allowable.
If a key is not allowed by :func:`~json.dumps`, then return its `str` representation.
Args:
k (Any): Key.
Returns:
A JSON-allowable key, which can be str, int, float, bool or None.
"""
if isinstance(k, (str, int, float, bool)):
parsed = k
elif k is None:
parsed = k
else:
parsed = str(k)
return parsed
[docs]
def parse(o: Any) -> Any:
"""Make a Python object JSON-allowable.
Args:
o (any): Python object.
Returns:
Any: JSON-allowable object.
"""
if isinstance(o, list):
parsed = [parse(v) for v in o]
elif isinstance(o, tuple):
parsed = [parse(v) for v in o]
elif isinstance(o, dict):
parsed = {key_parse(k): parse(v) for k, v in o.items()}
else:
parsed = value_parse(o)
return parsed
[docs]
def sort_hashable_ahead(o: dict) -> dict:
"""Make hashable values be the ahead in dictionary."
Args:
o (dict): Unsorted dictionary.
Returns:
dict: Sorted dictionary.
"""
sort_o = OrderedDict()
for k, v in o.items():
if isinstance(v, Hashable):
sort_o[k] = v
for k, v in o.items():
if k not in sort_o:
sort_o[k] = v
return sort_o
# pylint: disable=invalid-name
[docs]
def quickJSON(
content: Iterable,
filename: Union[str, Path],
mode: str,
indent: int = DEFAULT_INDENT,
encoding: str = DEFAULT_ENCODING,
jsonable: bool = False,
save_location: Union[Path, str] = Path("./"),
mute: bool = True,
) -> Optional[str]:
"""Configurable quick JSON export.
Args:
content (any): Content wants to be written.
filename (str): Filename of the file.
mode (str): Mode for :func:`open` function.
indent (int, optional): Indent length for json. Defaults to 2.
encoding (str, optional): Encoding method. Defaults to 'utf-8'.
jsonablize (bool, optional):
Whether to transpile all object to JSON-allowable object.
If True, it will use :func:`parse` to transpile the content.
Defaults to False.
save_location (Union[Path, str], optional): Location of files. Defaults to Path('./').
mute (bool, optional): Mute the exportation. Defaults to True.
Returns:
Optional[str]: The filename of the file when not mute.
"""
if not isinstance(save_location, Path):
save_location = Path(save_location)
if not os.path.exists(save_location):
os.makedirs(save_location)
save_loc_w_name = save_location / filename
with open(save_loc_w_name, mode, encoding=encoding) as file:
if jsonable:
json.dump(parse(content), file, indent=indent, ensure_ascii=False)
else:
json.dump(content, file, indent=indent, ensure_ascii=False)
if not mute:
return f"'{save_loc_w_name}' exported successfully."
# pylint: enable=invalid-name