@@ -422,9 +422,25 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
422422 "maxProperties" , math .inf
423423 ):
424424 type_ .remove ("object" )
425- # Remove no-op requires
426- if "required" in schema and not schema ["required" ]:
427- schema .pop ("required" )
425+ # Discard dependencies values that don't restrict anything
426+ for k , v in schema .get ("dependencies" , {}).copy ().items ():
427+ if v == [] or v == TRUTHY :
428+ schema ["dependencies" ].pop (k )
429+ # Remove no-op keywords
430+ for kw , identity in {
431+ "minItems" : 0 ,
432+ "items" : {},
433+ "additionalItems" : {},
434+ "dependencies" : {},
435+ "minProperties" : 0 ,
436+ "properties" : {},
437+ "propertyNames" : {},
438+ "patternProperties" : {},
439+ "additionalProperties" : {},
440+ "required" : [],
441+ }.items ():
442+ if kw in schema and schema [kw ] == identity :
443+ schema .pop (kw )
428444 # Canonicalise "required" schemas to remove redundancy
429445 if "object" in type_ and "required" in schema :
430446 assert isinstance (schema ["required" ], list )
@@ -433,16 +449,18 @@ def canonicalish(schema: JSONType) -> Dict[str, Any]:
433449 # When the presence of a required property requires other properties via
434450 # dependencies, those properties can be moved to the base required keys.
435451 dep_names = {
436- k : sorted (v )
452+ k : sorted (set ( v ) )
437453 for k , v in schema ["dependencies" ].items ()
438454 if isinstance (v , list )
439455 }
456+ schema ["dependencies" ].update (dep_names )
440457 while reqs .intersection (dep_names ):
441458 for r in reqs .intersection (dep_names ):
442459 reqs .update (dep_names .pop (r ))
443- for k , v in list (schema ["dependencies" ].items ()):
444- if isinstance (v , list ) and k not in dep_names :
445- schema ["dependencies" ].pop (k )
460+ schema ["dependencies" ].pop (r )
461+ # TODO: else merge schema-dependencies of required properties
462+ # into the base schema after adding required back in and being
463+ # careful to avoid an infinite loop...
446464 schema ["required" ] = sorted (reqs )
447465 max_ = schema .get ("maxProperties" , float ("inf" ))
448466 assert isinstance (max_ , (int , float ))
@@ -782,9 +800,37 @@ def merged(schemas: List[Any]) -> Optional[Schema]:
782800 s .pop ("contains" )
783801 if "not" in out and "not" in s and out ["not" ] != s ["not" ]:
784802 out ["not" ] = {"anyOf" : [out ["not" ], s .pop ("not" )]}
803+ if (
804+ "dependencies" in out
805+ and "dependencies" in s
806+ and out ["dependencies" ] != s ["dependencies" ]
807+ ):
808+ # Note: draft 2019-09 added separate keywords for name-dependencies
809+ # and schema-dependencies, but when we add support for that it will
810+ # be by canonicalising to the existing backwards-compatible keyword.
811+ #
812+ # In each dependencies dict, the keys are property names and the values
813+ # are either a list of required names, or a schema that the whole
814+ # instance must match. To merge a list and a schema, convert the
815+ # former into a `required` key!
816+ odeps = out ["dependencies" ]
817+ for k , v in odeps .copy ().items ():
818+ if k in s ["dependencies" ]:
819+ sval = s ["dependencies" ].pop (k )
820+ if isinstance (v , list ) and isinstance (sval , list ):
821+ odeps [k ] = v + sval
822+ continue
823+ if isinstance (v , list ):
824+ v = {"required" : v }
825+ elif isinstance (sval , list ):
826+ sval = {"required" : sval }
827+ m = merged ([v , sval ])
828+ if m is None :
829+ return None
830+ odeps [k ] = m
831+ odeps .update (s .pop ("dependencies" ))
785832
786833 # TODO: merge `items` schemas or lists-of-schemas
787- # TODO: merge dependencies
788834
789835 # This loop handles the remaining cases. Notably, we do not attempt to
790836 # merge distinct values for:
0 commit comments