--- /dev/null
+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)
--- /dev/null
+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'])