From 2bd25c718550fcd37809719d658247721a207989 Mon Sep 17 00:00:00 2001 From: Vineeth Sai Date: Thu, 16 Jul 2026 12:56:44 -0700 Subject: [PATCH] Fix find_additional_properties ignoring an empty patternProperties key find_additional_properties joined all patternProperties keys into a single '|'.join(...) regex and guarded it with 'if patterns', which is falsy for an empty string. An empty-string pattern key (a valid regex matching every property) was therefore dropped, so its properties were wrongly treated as additional and validated against additionalProperties instead of the pattern subschema. The sibling find_evaluated_property_keys_by_schema already iterates the patterns individually with re.search; mirror that here. --- jsonschema/_utils.py | 11 ++++++----- jsonschema/tests/test_utils.py | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/jsonschema/_utils.py b/jsonschema/_utils.py index 3177caea3..b880e8f66 100644 --- a/jsonschema/_utils.py +++ b/jsonschema/_utils.py @@ -82,12 +82,13 @@ def find_additional_properties(instance, schema): Assumes ``instance`` is dict-like already. """ properties = schema.get("properties", {}) - patterns = "|".join(schema.get("patternProperties", {})) + patterns = schema.get("patternProperties", {}) for property in instance: - if property not in properties: - if patterns and re.search(patterns, property): - continue - yield property + if property in properties: + continue + if any(re.search(pattern, property) for pattern in patterns): + continue + yield property def extras_msg(extras): diff --git a/jsonschema/tests/test_utils.py b/jsonschema/tests/test_utils.py index f716a36a1..387c4dbdb 100644 --- a/jsonschema/tests/test_utils.py +++ b/jsonschema/tests/test_utils.py @@ -2,7 +2,7 @@ from math import nan from unittest import TestCase -from jsonschema._utils import equal, uniq +from jsonschema._utils import equal, find_additional_properties, uniq class Unhashable: @@ -223,3 +223,21 @@ def test_unhashable_inside_mapping_falls_back(self): self.assertTrue( uniq([{"k": Unhashable(1)}, {"k": Unhashable(2)}]), ) + + +class TestFindAdditionalProperties(TestCase): + def test_empty_pattern_matches_all_properties(self): + # An empty-string pattern is a valid regex that matches every property, + # so patternProperties covers everything and nothing is additional. + schema = {"patternProperties": {"": {"type": "integer"}}} + instance = {"foo": 1, "bar": 2} + self.assertEqual( + list(find_additional_properties(instance, schema)), [], + ) + + def test_no_pattern_properties_leaves_all_additional(self): + schema = {"properties": {"foo": {}}} + instance = {"foo": 1, "bar": 2} + self.assertEqual( + list(find_additional_properties(instance, schema)), ["bar"], + )