diff --git a/src/datamodel_code_generator/parser/jsonschema.py b/src/datamodel_code_generator/parser/jsonschema.py index 472d26f66..ab7687079 100644 --- a/src/datamodel_code_generator/parser/jsonschema.py +++ b/src/datamodel_code_generator/parser/jsonschema.py @@ -460,14 +460,19 @@ def has_ref_with_schema_keywords(self) -> bool: """Check if schema has $ref combined with schema-affecting keywords. Metadata-only keywords (title, description, etc.) are excluded - as they don't affect the schema structure. + as they don't affect the schema structure. OpenAPI/JSON Schema + extension fields (x-*) are also excluded as they are vendor + extensions and don't affect the core schema structure. """ if not self.ref: return False other_fields = get_fields_set(self) - {"ref"} schema_affecting_fields = other_fields - self.__metadata_only_fields__ - {"extras"} if self.extras: - schema_affecting_extras = {k for k in self.extras if k not in self.__metadata_only_fields__} + # Filter out metadata-only fields AND extension fields (x-* prefix) + schema_affecting_extras = { + k for k in self.extras if k not in self.__metadata_only_fields__ and not k.startswith("x-") + } if schema_affecting_extras: schema_affecting_fields |= {"extras"} return bool(schema_affecting_fields) diff --git a/tests/parser/test_jsonschema.py b/tests/parser/test_jsonschema.py index 4452dce3c..5bb7efea7 100644 --- a/tests/parser/test_jsonschema.py +++ b/tests/parser/test_jsonschema.py @@ -915,6 +915,31 @@ def test_has_ref_with_schema_keywords_extras_with_metadata_only_keys() -> None: assert obj.has_ref_with_schema_keywords is False +def test_has_ref_with_schema_keywords_extras_with_extension_keys() -> None: + """Test has_ref_with_schema_keywords when extras contains only x-* extension keys. + + OpenAPI/JSON Schema extension fields (x-*) should be treated as metadata + and not trigger schema merging, which prevents infinite recursion with + self-referencing schemas. + """ + # x-* extensions are vendor extensions, should not trigger merge + obj = model_validate( + JsonSchemaObject, + { + "$ref": "#/$defs/Base", + "deprecated": False, # metadata-only field + "x-internalAPI": False, # extension field + "x-custom-field": "value", # another extension field + }, + ) + # Verify extras contains extension keys + assert obj.extras + assert "x-internalAPI" in obj.extras + assert "x-custom-field" in obj.extras + # Extension fields should NOT trigger schema merge + assert obj.has_ref_with_schema_keywords is False + + def test_has_ref_with_schema_keywords_no_extras() -> None: """Test has_ref_with_schema_keywords when extras is empty.""" # Only $ref and a schema-affecting field, no extras