hat.json - Python JSON data library¶
Hat uses JSON data as simple data structures supported by various platforms with well supported human-readable encoding formats.
Under JSON data, following data types are assumed:
constants
null
,true
andfalse
Numbers (integer and floating point)
String
Array
Object
Python data mappings¶
Mapping between JSON data types and built in types:
JSON data
Python data
null
None
true
,false
bool
Number
int, float
String
str
Array
list
Object
dict
According to previous mapping, this library defines following types:
Array: typing.TypeAlias = typing.List['Data']
Object: typing.TypeAlias = typing.Dict[str, 'Data']
Data: typing.TypeAlias = None | bool | int | float | str | Array | Object
Because in Python bool is subtype of int, ==
operator can’t be used
if strict comparison between JSON data is required. In this cases, function
hat.json.equals can be used:
def equals(a: Data, b: Data) -> bool: ...
Example usage:
assert equals(0, 0.0) is True
assert equals({'a': 1, 'b': 2}, {'b': 2, 'a': 1}) is True
assert equals(1, True) is False
Function hat.json.clone enables deep cloning of composite JSON data:
def clone(data: Data) -> Data: ...
Example usage:
x = {'a': [1, 2, 3]}
y = clone(x)
assert x is not y
assert x['a'] is not y['a']
assert equals(x, y)
Additional utility function hat.json.flatten is provided. This generator is used for recursively flattening of Array’s:
def flatten(data: Data) -> typing.Iterable[Data]: ...
Example usage:
data = [1, [], [2], {'a': [3]}]
result = [1, 2, {'a': [3]}]
assert list(flatten(data)) == result
JSON data path¶
Path can be used as efficient reference to subset of deeply nested JSON data structure:
Path: typing.TypeAlias = int | str | typing.List['Path']
Determining data subset is defined by recursive algorithm which takes into account Path type:
int
References specific element of input Array with index equal to path.
str
References specific element of input Object with key equal to path.
list
If path is empty list, input data as a whole is referenced. If list has at least one element and
head, rest = path[0], path[1:]
, referenced data is equal of applyingrest
path to data obtained as result of applyinghead
path onto input data.
Function hat.json.get is used for obtaining subset of input data referenced by path. If referenced subset doesn’t exist, this function returns default value:
def get(data: Data, path: Path, default: Data | None = None) -> Data: ...
Example usage:
data = {'a': [1, 2, [3, 4]]}
path = ['a', 2, 0]
assert get(data, path) == 3
data = [1, 2, 3]
assert get(data, 0) == 1
assert get(data, 5) is None
assert get(data, 5, default=123) == 123
Function hat.json.set_ is used for creating new data based on input data where subset of input data is replaced by provided input value. This function doesn’t modify input data and tries to optimally reuse parts of input data which are the same as in output data:
def set_(data: Data, path: Path, value: Data) -> Data: ...
Example usage:
data = [1, {'a': 2, 'b': 3}, 4]
path = [1, 'b']
result = set_(data, path, 5)
assert result == [1, {'a': 2, 'b': 5}, 4]
assert result is not data
data = [1, 2, 3]
result = set_(data, 4, 4)
assert result == [1, 2, 3, None, 4]
Function hat.json.remove is used for creating new data based on input data where subset of input data referenced by path is removed. This function doesn’t modify input data and tries to optimally reuse parts of input data which are the same as in output data:
def remove(data: Data, path: Path) -> Data: ...
Example usage:
data = [1, {'a': 2, 'b': 3}, 4]
path = [1, 'b']
result = remove(data, path)
assert result == [1, {'a': 2}, 4]
assert result is not data
data = [1, 2, 3]
result = remove(data, 4)
assert result == [1, 2, 3]
JSON patch¶
Function hat.json.diff and hat.json.patch provide simple wrappers for jsonpatch library (implementation of JSON Patch):
def diff(src: Data, dst: Data) -> Data: ...
def patch(data: Data, diff: Data) -> Data: ...
Example usage:
src = [1, {'a': 2}, 3]
dst = [1, {'a': 4}, 3]
result = diff(src, dst)
assert result == [{'op': 'replace', 'path': '/1/a', 'value': 4}]
data = [1, {'a': 2}, 3]
d = [{'op': 'replace', 'path': '/1/a', 'value': 4}]
result = patch(data, d)
assert result == [1, {'a': 4}, 3]
Encoding/decoding¶
Encoding of JSON data can be based on JSON, YAML or TOML format:
class Format(enum.Enum):
JSON = 'json'
YAML = 'yaml'
TOML = 'toml'
Encoding/decoding implementations used in hat.json are based on json standard library , PyYAML library, tomli library and tomli-w library .
For encoding to string, functions hat.json.encode and hat.json.decode can be used:
def encode(data: Data,
format: Format = Format.JSON,
indent: int | None = None
) -> str:
def decode(data_str: str,
format: Format = Format.JSON
) -> Data:
For encoding to file, functions hat.json.encode_file and hat.json.decode_file can be used. If format is not set, it will be derived from path suffix:
def encode_file(data: Data,
path: pathlib.PurePath,
format: Format | None = None,
indent: int | None = 4):
def decode_file(path: pathlib.PurePath,
format: Format | None = None
) -> Data:
If encoding to opened streams is required, functions hat.json.encode_stream and hat.json.decode_stream can be used:
def encode_stream(data: Data,
stream: io.TextIOBase | io.RawIOBase,
format: Format = Format.JSON,
indent: int | None = 4):
def decode_stream(stream: io.TextIOBase | io.RawIOBase,
format: Format = Format.JSON
) -> Data:
JSON Schema¶
JSON Schema provides means for definition and validation of JSON data structures.
hat.json.SchemaRepository provides wrapper for jsonschema library with ability to utilize multiple interconnected JSON schemas.
All schemas combined in single SchemaRepository can be serialized as JSON data.
class SchemaRepository:
def __init__(self, *args: typing.Union[pathlib.PurePath,
Data,
'SchemaRepository']): ...
def validate(self,
schema_id: str,
data: Data): ...
def to_json(self) -> Data: ...
@staticmethod
def from_json(data: pathlib.PurePath | Data
) -> 'SchemaRepository': ...
API¶
API reference is available as part of generated documentation: