From 4c3881573385e20f5a238dc3b9a398687006872f Mon Sep 17 00:00:00 2001 From: Cory Todd Date: Tue, 5 Sep 2023 14:05:50 -0700 Subject: [PATCH] introduce Serializer class We have never handled the JSONDecodeError so consumers are assumed to be catching that exception on their end. This means we cannot wrap all the parser errors into a unified type (e.g. ValueError) without breaking the API. Keep API compatibility on the loader functions by introducing a Serializer class that the cli tool can utilize. This allows the cli to avoid knowing which specific serializer errors to handle. The library itself maintains its current API. Signed-off-by: Cory Todd --- jsondiff/__init__.py | 51 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/jsondiff/__init__.py b/jsondiff/__init__.py index a984ad8..99556e2 100644 --- a/jsondiff/__init__.py +++ b/jsondiff/__init__.py @@ -4,6 +4,9 @@ import json import yaml +from json import JSONDecodeError +from yaml import YAMLError + from .symbols import * from .symbols import Symbol @@ -55,6 +58,10 @@ def __init__(self, **kwargs): self.kwargs = kwargs def __call__(self, src): + """Parse and return JSON data + :param src: str|file-like source + :return: dict parsed data + """ if isinstance(src, string_types): return json.loads(src, **self.kwargs) else: @@ -74,6 +81,47 @@ def __call__(self, src): """ return yaml.safe_load(src) +class Serializer: + """Serializer helper loads and stores object data + :param file_format: str json or yaml + :param indent: int Output indentation in spaces + :raise ValueError: file_path does not contains valid file_format data + """ + + def __init__(self, file_format, indent): + # pyyaml _can_ load json but is ~20 times slower and has known issues so use + # the json from stdlib when json is specified. + self.serializers = { + "json": (JsonLoader(), JsonDumper(indent=indent)), + "yaml": (YamlLoader(), YamlDumper(indent=indent)), + } + self.file_format = file_format + if file_format not in self.serializers: + raise ValueError(f"Unsupported serialization format {file_format}, expected one of {self.serializers.keys()}") + + def deserialize_file(self, src): + """Deserialize file from the specified format + :param file_path: str path to file + :param src: str|file-like source + :return dict + :raise ValueError: file_path does not contains valid file_format data + """ + loader, _ = self.serializers[self.file_format] + try: + parsed = loader(src) + except (JSONDecodeError, YAMLError) as ex: + raise ValueError(f"Invalid {self.file_format} file") from ex + return parsed + + def serialize_data(self, obj, stream): + """Serialize obj and write to stream + :param obj: dict to serialize + :param stream: Writeable stream + """ + _, dumper = self.serializers[self.file_format] + dumper(obj, stream) + + class JsonDiffSyntax(object): def emit_set_diff(self, a, b, s, added, removed): raise NotImplementedError() @@ -667,5 +715,6 @@ def similarity(a, b, cls=JsonDiffer, **kwargs): "JsonDumper", "JsonLoader", "YamlDumper", - "YamlLoader" + "YamlLoader", + "Serializer", ]