You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The JSON:API JSON Schema generator (ApiPlatform\JsonApi\JsonSchema\SchemaFactory) reconstructs the document shape independently from the runtime serializer. Two related weaknesses make the generated schema drift from the actual response. Surfaced while reviewing #8313 (and complementary to #8321, which fixes the lower-risk relationship-shape bugs). These are behavior-shifting, so targeting main / the next minor.
1. Reserved attribute names are only half-handled
buildDefinitionPropertiesSchema() renames a property named id to _id in attributes, hard-coded:
if ('id' === $propertyName) {
$attributes['_id'] = $property;
continue;
}
But the runtime ReservedAttributeNameConverter renames five names:
The base JSON schema is built with the generic api_platform.name_converter (see json_schema.php), not the reserved converter (wired only into the JSON:API normalizers in jsonapi.php). So a resource with a scalar property named type (a common field name) is documented as attributes.properties.type, while the response emits attributes._type. The documented key never matches the payload — the same doc/response mismatch #8308 was about, for four of the five reserved names.
Direction: map attribute names through ReservedAttributeNameConverter::JSON_API_RESERVED_ATTRIBUTES (single source of truth) instead of special-casing id. The relationship key rename (a relation literally named relationships/included) should be handled too.
2. (Altitude) The attribute/relationship split is duplicated from the normalizer
SchemaFactory::getRelationship() recomputes "is this property a relationship" from getNativeType() / getBuiltinTypes() — a near-verbatim copy of the runtime split in ItemNormalizer. Doc-time and runtime decide the attributes-vs-relationships partition through two independent implementations. Any change to one (new collection handling, union/intersection types, a TypeInfo migration) silently diverges the generated schema from the response — which is the root mechanism behind the whole #8308 class of bugs.
Direction: derive the schema's attribute/relationship split (and reserved-name mapping) from the same component(s) that produce the runtime document, so they cannot drift.
Out of scope but related (low priority)
Found in the same review, intentionally not fixed in #8321 (low value / cross-cutting risk):
Orphaned base definition. After fix(jsonapi): exclude relations from openapi attributes schema #8313 nothing references the plain #/definitions/<Class> schema, yet it still ships in components.schemas — an unused component (and still the relations-as-objects/bare-id shape the fix removed from attributes).
$builtSchema shared factory state. The instance cache is never reset; per-operation schemas built on the shared service can be internally incomplete (mitigated in OpenAPI output because OpenApiFactory merges every build's definitions, so the merged document is not left dangling).
Acceptance
A resource property named any of type/links/relationships/included is documented under the same key the response uses.
Schema attribute/relationship split provably matches ItemNormalizer output (shared derivation or a contract test over the fixtures).
Summary
The JSON:API JSON Schema generator (
ApiPlatform\JsonApi\JsonSchema\SchemaFactory) reconstructs the document shape independently from the runtime serializer. Two related weaknesses make the generated schema drift from the actual response. Surfaced while reviewing #8313 (and complementary to #8321, which fixes the lower-risk relationship-shape bugs). These are behavior-shifting, so targetingmain/ the next minor.1. Reserved attribute names are only half-handled
buildDefinitionPropertiesSchema()renames a property namedidto_idinattributes, hard-coded:But the runtime
ReservedAttributeNameConverterrenames five names:The base JSON schema is built with the generic
api_platform.name_converter(seejson_schema.php), not the reserved converter (wired only into the JSON:API normalizers injsonapi.php). So a resource with a scalar property namedtype(a common field name) is documented asattributes.properties.type, while the response emitsattributes._type. The documented key never matches the payload — the same doc/response mismatch #8308 was about, for four of the five reserved names.Direction: map attribute names through
ReservedAttributeNameConverter::JSON_API_RESERVED_ATTRIBUTES(single source of truth) instead of special-casingid. The relationship key rename (a relation literally namedrelationships/included) should be handled too.2. (Altitude) The attribute/relationship split is duplicated from the normalizer
SchemaFactory::getRelationship()recomputes "is this property a relationship" fromgetNativeType()/getBuiltinTypes()— a near-verbatim copy of the runtime split inItemNormalizer. Doc-time and runtime decide the attributes-vs-relationships partition through two independent implementations. Any change to one (new collection handling, union/intersection types, a TypeInfo migration) silently diverges the generated schema from the response — which is the root mechanism behind the whole #8308 class of bugs.Direction: derive the schema's attribute/relationship split (and reserved-name mapping) from the same component(s) that produce the runtime document, so they cannot drift.
Out of scope but related (low priority)
Found in the same review, intentionally not fixed in #8321 (low value / cross-cutting risk):
#/definitions/<Class>schema, yet it still ships incomponents.schemas— an unused component (and still the relations-as-objects/bare-idshape the fix removed fromattributes).$builtSchemashared factory state. The instance cache is never reset; per-operation schemas built on the shared service can be internally incomplete (mitigated in OpenAPI output becauseOpenApiFactorymerges every build's definitions, so the merged document is not left dangling).Acceptance
type/links/relationships/includedis documented under the same key the response uses.ItemNormalizeroutput (shared derivation or a contract test over the fixtures).