1- import copy
21import math
32from typing import cast
43from typing import Dict
1110from jschon .json import JSONCompatible
1211
1312
13+ _END_SORT_KEY = (math .inf ,)
14+
15+
1416def _get_sort_keys_for_json_nodes (root_node : jschon .JSON ) -> Mapping [jschon .JSONPointer , Tuple [int , ...]]:
1517 """
1618 Gets a mapping from JSON nodes (as JSON pointers) to sort keys (as tuples of integers) that match their position
@@ -39,20 +41,7 @@ def _recurse(node: jschon.JSON, node_sort_key: Tuple[int, ...]) -> None:
3941 return mapping
4042
4143
42- def sort_doc_by_schema (* , doc_data : JSONCompatible , schema_data : Mapping [str , JSONCompatible ]) -> JSONCompatible :
43- try :
44- root_schema = jschon .JSONSchema (schema_data )
45- except jschon .CatalogError :
46- # jschon only supports newer jsonschema drafts
47- schema_data = dict (schema_data )
48- schema_data ['$schema' ] = "https://json-schema.org/draft/2020-12/schema"
49- root_schema = jschon .JSONSchema (schema_data )
50-
51- doc_json = jschon .JSON (doc_data )
52- res = root_schema .evaluate (doc_json )
53- if not res .valid :
54- raise ValueError ('Document failed schema validation' )
55-
44+ def _get_sort_keys_for_json_doc (* , root_scope : jschon .jsonschema .Scope ) -> Mapping [jschon .JSONPointer , Tuple [int , ...]]:
5645 schema_sort_keys_cache : Dict [jschon .URI , Mapping [jschon .JSONPointer , Tuple [int , ...]]] = {}
5746
5847 def _get_sort_keys_for_schema (schema : jschon .JSONSchema ) -> Mapping [jschon .JSONPointer , Tuple [int , ...]]:
@@ -73,11 +62,37 @@ def _traverse_scope(scope: jschon.jsonschema.Scope) -> None:
7362 for child in scope .iter_children ():
7463 _traverse_scope (child )
7564
76- _traverse_scope (res )
65+ _traverse_scope (root_scope )
7766
78- end_sort_key = (math .inf ,)
67+ return doc_sort_keys
68+
69+
70+ def _get_root_scope (doc_json : jschon .JSON , schema_data : Mapping [str , JSONCompatible ]) -> jschon .jsonschema .Scope :
71+ try :
72+ root_schema = jschon .JSONSchema (schema_data )
73+ except jschon .CatalogError :
74+ # jschon only supports newer jsonschema drafts
75+ schema_data = dict (schema_data )
76+ schema_data ['$schema' ] = "https://json-schema.org/draft/2020-12/schema"
77+ root_schema = jschon .JSONSchema (schema_data )
78+ res = root_schema .evaluate (doc_json )
79+ if not res .valid :
80+ raise ValueError ('Document failed schema validation' )
81+ return res
82+
83+
84+ def process_json_doc (
85+ * ,
86+ doc_data : JSONCompatible ,
87+ schema_data : Mapping [str , JSONCompatible ],
88+ sort : bool = False ,
89+ remove_additional_props : bool = False ,
90+ ) -> JSONCompatible :
91+ doc_json = jschon .JSON (doc_data )
92+ root_scope = _get_root_scope (doc_json , schema_data = schema_data )
93+ doc_sort_keys = _get_sort_keys_for_json_doc (root_scope = root_scope )
7994
80- def _sort_json_node (node : JSONCompatible , json_node : jschon .JSON ) -> JSONCompatible :
95+ def _traverse_node (node : JSONCompatible , json_node : jschon .JSON ) -> JSONCompatible :
8196 """
8297 @param node: the node being traversed (the data)
8398 @param json_node: the node being traversed (jschon's representation)
@@ -93,14 +108,18 @@ def _sort_json_node(node: JSONCompatible, json_node: jschon.JSON) -> JSONCompati
93108 v : JSONCompatible
94109 v_json : jschon .JSON
95110 for (k , v ), v_json in zip (node .items (), object_data .values ()):
96- properties . append (( k , _sort_json_node (v , v_json )) )
111+ v = _traverse_node (v , v_json )
97112 # Keys which don't map to the schema (e.g. undefined properties when additionalProperties is missing,
98113 # defaulting to true) are assumed to come last (end_sort_key).
99114 # As a tie breaker for multiple such undefined properties, we use the key's name.
100115 # TODO: update jschon to add additional properties to res.children when appropriate
101- key_sort_keys [k ] = doc_sort_keys .get (v_json .path , end_sort_key ), k
116+ sk = doc_sort_keys .get (v_json .path , _END_SORT_KEY )
117+ if sk is not _END_SORT_KEY or not remove_additional_props :
118+ key_sort_keys [k ] = sk , k
119+ properties .append ((k , v ))
102120
103- properties .sort (key = lambda pair : key_sort_keys [pair [0 ]])
121+ if sort :
122+ properties .sort (key = lambda pair : key_sort_keys [pair [0 ]])
104123
105124 # to maintain YAML round-trip data, copy node and re-populate
106125 node_copy = node .copy ()
@@ -111,10 +130,10 @@ def _sort_json_node(node: JSONCompatible, json_node: jschon.JSON) -> JSONCompati
111130
112131 elif isinstance (node , list ):
113132 list_data = cast (Sequence [jschon .JSON ], json_node .data )
114- return [_sort_json_node (node [idx ], v_json ) for idx , v_json in enumerate (list_data )]
133+ return [_traverse_node (node [idx ], v_json ) for idx , v_json in enumerate (list_data )]
115134
116135 return node
117136
118137 # we recurse down both the "JSON" and the actual document, and mutate only the actual document
119138 # which is the primitive type that we can serialize back to JSON/YAML easily
120- return _sort_json_node (doc_data , doc_json )
139+ return _traverse_node (doc_data , doc_json )
0 commit comments