@@ -192,7 +192,12 @@ class JSONReference(_enum.Enum):
192192
193193
194194class Discriminator (BaseModel ):
195- """Represent OpenAPI discriminator object."""
195+ """Represent OpenAPI discriminator object.
196+
197+ This is an OpenAPI-specific concept for supporting polymorphism.
198+ It identifies which schema applies based on a property value.
199+ Kept in jsonschema.py to avoid circular imports with openapi.py.
200+ """
196201
197202 propertyName : str # noqa: N815
198203 mapping : Optional [dict [str , str ]] = None # noqa: UP045
@@ -768,8 +773,16 @@ def _get_type_with_mappings(self, type_: str, format_: str | None = None) -> Typ
768773
769774 @cached_property
770775 def schema_paths (self ) -> list [tuple [str , list [str ]]]:
771- """Get schema paths for definitions and defs."""
772- return [(s , s .lstrip ("#/" ).split ("/" )) for s in self .SCHEMA_PATHS ]
776+ """Get schema paths for definitions and defs.
777+
778+ Prioritizes the version-appropriate key based on detected schema version,
779+ but checks both for lenient compatibility with mixed-version schemas.
780+ - Draft 4-7: definitions (standard), then $defs (fallback)
781+ - Draft 2019-09+: $defs (standard), then definitions (fallback)
782+ """
783+ primary_key = self .schema_features .definitions_key
784+ ordered_paths = ["#/$defs" , "#/definitions" ] if primary_key == "$defs" else ["#/definitions" , "#/$defs" ]
785+ return [(s , s .lstrip ("#/" ).split ("/" )) for s in ordered_paths ]
773786
774787 @cached_property
775788 def schema_features (self ) -> JsonSchemaFeatures :
@@ -913,6 +926,19 @@ def is_constraints_field(self, obj: JsonSchemaObject) -> bool:
913926 )
914927 )
915928
929+ @staticmethod
930+ def _get_array_items (obj : JsonSchemaObject ) -> list [JsonSchemaObject ] | JsonSchemaObject | bool | None :
931+ """Get array items from schema, handling both prefixItems (2020-12) and items (older).
932+
933+ In lenient mode (default), checks both fields for compatibility with mixed-version schemas.
934+ Prioritizes prefixItems if both are present (2020-12 semantics).
935+ """
936+ # Check prefixItems first (Draft 2020-12) if schema version supports it or present
937+ if obj .prefixItems is not None :
938+ return obj .prefixItems
939+ # Fall back to items for older drafts or when prefixItems is not present
940+ return obj .items
941+
916942 def _is_fixed_length_tuple (self , obj : JsonSchemaObject ) -> bool :
917943 """Check if an array field represents a fixed-length tuple."""
918944 if obj .prefixItems is not None and obj .items in {None , False }:
@@ -3589,9 +3615,15 @@ def parse_id(self, obj: JsonSchemaObject, path: list[str]) -> None:
35893615
35903616 @contextmanager
35913617 def root_id_context (self , root_raw : dict [str , Any ]) -> Generator [None , None , None ]:
3592- """Context manager to temporarily set the root $id during parsing."""
3618+ """Context manager to temporarily set the root $id during parsing.
3619+
3620+ Uses schema_features.id_field to support both "id" (Draft 4) and "$id" (Draft 6+).
3621+ Falls back to checking both fields for lenient compatibility.
3622+ """
35933623 previous_root_id = self .root_id
3594- self .root_id = root_raw .get ("$id" ) or None
3624+ # Try version-specific field first, then fallback to alternative for compatibility
3625+ id_field = self .schema_features .id_field
3626+ self .root_id = root_raw .get (id_field ) or root_raw .get ("$id" ) or root_raw .get ("id" ) or None
35953627 yield
35963628 self .root_id = previous_root_id
35973629
0 commit comments