]> ToastFreeware Gitweb - philipp/winterrodeln/wrpylib.git/commitdiff
Start to work on function sorting JSON data based on JSON schema.
authorPhilipp Spitzer <philipp@spitzer.priv.at>
Tue, 26 Oct 2021 21:08:49 +0000 (23:08 +0200)
committerPhilipp Spitzer <philipp@spitzer.priv.at>
Tue, 26 Oct 2021 21:08:49 +0000 (23:08 +0200)
tests/test_json_validate.py [new file with mode: 0644]
wrpylib/json_tools.py [new file with mode: 0644]

diff --git a/tests/test_json_validate.py b/tests/test_json_validate.py
new file mode 100644 (file)
index 0000000..64afcf1
--- /dev/null
@@ -0,0 +1,120 @@
+import unittest
+
+from wrpylib.json_tools import order_json_keys
+
+
+schema_object = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "title": "Rodelbahn",
+    "type": "object",
+    "required": [
+        "name"
+    ],
+    "properties": {
+        "name": {
+            "title": "Rodelbahn Name",
+            "type": "string",
+        },
+        "aliases": {
+            "title": "Alternative Namen",
+            "type": "array",
+            "items": {
+                "type": "string",
+                "title": "Alternativer Name"
+            }
+        },
+        "entry_under_construction": {
+            "title": "Eintrag in Arbeit",
+            "type": "boolean",
+            "default": True,
+        },
+        "length": {
+            "title": "Länge",
+            "type": "number",
+            "minimum": 1,
+            "optional": True
+        },
+        "difficulty": {
+            "title": "Schwierigkeit",
+            "type": "string",
+            "enum": [
+                "leicht",
+                "mittel",
+                "schwer"
+            ],
+        },
+        "walkup_time": {
+            "title": "Gehzeit",
+            "type": "number",
+        },
+    }
+}
+
+
+class TestJsonValidate(unittest.TestCase):
+    def test_empty_object(self):
+        schema = schema_object.copy()
+        del schema['required']
+        actual = order_json_keys({}, schema)
+        self.assertEqual({}, actual)
+
+    def test_object_missing_keys(self):
+        with self.assertRaises(ValueError):
+            order_json_keys({}, schema_object)
+
+    def test_object_some_keys(self):
+        actual = order_json_keys({'name': 'X', 'walkup_time': 30, 'difficulty': 'mittel'}, schema_object)
+        self.assertEqual({'name': 'X', 'difficulty': 'mittel', 'walkup_time': 30}, actual)
+
+    def test_object_all_keys(self):
+        value = {
+            'walkup_time': 120,
+            'aliases': ['c', 'a', 'b'],
+            'entry_under_construction': False,
+            'name': 'ÖÄÜ',
+            'difficulty': 'leicht',
+            'length': 60,
+        }
+        expected = {
+            'name': 'ÖÄÜ',
+            'aliases': ['c', 'a', 'b'],
+            'entry_under_construction': False,
+            'length': 60,
+            'difficulty': 'leicht',
+            'walkup_time': 120,
+        }
+        actual = order_json_keys(value, schema_object)
+        self.assertEqual(expected, actual)
+
+    def test_object_additional_keys(self):
+        value = {
+            'walkup_time': 120,
+            'aliases': ['c', 'a', 'b'],
+            'entry_under_construction': False,
+            'name': 'ÖÄÜ',
+            'surprise': True,
+            'difficulty': 'leicht',
+            'length': 60,
+        }
+        expected = {
+            'name': 'ÖÄÜ',
+            'aliases': ['c', 'a', 'b'],
+            'entry_under_construction': False,
+            'length': 60,
+            'difficulty': 'leicht',
+            'walkup_time': 120,
+            'surprise': True,
+        }
+        actual = order_json_keys(value, schema_object)
+        self.assertEqual(expected, actual)
+
+    def test_object_forbidden_additional_keys(self):
+        value = {
+            'name': 'abc',
+            'surprise': True,
+            'difficulty': 'leicht',
+        }
+        schema = schema_object.copy()
+        schema['additionalProperties'] = False
+        with self.assertRaises(ValueError):
+            order_json_keys(value, schema)
diff --git a/wrpylib/json_tools.py b/wrpylib/json_tools.py
new file mode 100644 (file)
index 0000000..2adacb9
--- /dev/null
@@ -0,0 +1,35 @@
+from typing import Union, Dict, List
+
+
+JsonTypes = Union[Dict, List, str, int, float, bool, None]
+
+
+class ValidationError(ValueError):
+    pass
+
+
+def _order_json_keys(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> JsonTypes:
+    if sub_schema['type'] == 'object':
+        if not isinstance(sub_value, dict):
+            raise ValidationError(f'Type of {"".join(path)} needs to be object (Python dict).')
+        v = sub_value.copy()
+        p = sub_schema['properties']
+        result = {}
+        for key in p:
+            if key in v:
+                result[key] = _order_json_keys(v.pop(key), p[key], path + [f"['{key}']"])
+            else:
+                if key in sub_schema.get('required', []):
+                    raise ValidationError(f'Required key "{key}" not present ({"".join(path)}).')
+        if len(v) > 0:
+            if sub_schema.get('additionalProperties', True):
+                # strictly speaking additionalProperties could be more complicated than boolean
+                result.update(v)
+            else:
+                raise ValidationError(f'Keys not allowed in {"".join(path)}: {", ".join(v)}')
+        return result
+    return sub_value
+
+
+def order_json_keys(value: JsonTypes, schema: JsonTypes) -> JsonTypes:
+    return _order_json_keys(value, schema, ['value'])