JSON:API: input schema puts an embedded input DTO collection under `relationships`, but the denormalizer reads it from `attributes`
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
调研方向
Start in JsonApi\JsonSchema\SchemaFactory::buildDefinitionPropertiesSchema(), where getRelationship() is called with the resource class instead of the input class. Compare how the denormalizer splits attributes from relationships for an input DTO, then reproduce with the issue's Order/OrderInput classes and run bin/console api:openapi:export. Done when lines is documented under attributes as an array of OrderLineInput for the input schema, and the output schema is unchanged.
由索引模型根据 Issue 内容生成。
描述
API Platform version(s) affected
4.4.4
Description
A resource has a relation to another resource. A write operation uses an input DTO, and the DTO has a property with the same name that holds plain (non-resource) objects.
For the JSON:API format, the generated request body schema puts this property under data.relationships as resource linkage (type + id). The denormalizer does not accept that shape. It accepts the objects only under data.attributes, and that shape is not documented.
The cause looks like JsonApi\JsonSchema\SchemaFactory::buildDefinitionPropertiesSchema(). It calls getRelationship($className, ...) with the resource class, not with the input class. So the schema uses the type of the resource property (list<OrderLine>, a resource) and not the type of the input property (list<OrderLineInput>, a plain DTO).
How to reproduce
#[ApiResource(operations: [new Get(provider: [self::class, 'provide'])])]
final class OrderLine
{
public function __construct(public int $id = 1, public string $sku = 'ABC', public int $quantity = 1) {}
public static function provide(): self { return new self(); }
}
final class OrderLineInput
{
public string $sku;
public int $quantity;
}
final class OrderInput
{
public string $reference;
/** @var list<OrderLineInput> */
public array $lines = [];
}
#[ApiResource(operations: [
new Get(provider: [self::class, 'provide']),
new Post(input: OrderInput::class, processor: [self::class, 'process']),
])]
final class Order
{
/** @param list<OrderLine> $lines */
public function __construct(public int $id = 1, public string $reference = 'R-1', public array $lines = []) {}
public static function provide(): self { return new self(); }
public static function process(OrderInput $data, Operation $operation): self
{
$lines = [];
foreach ($data->lines as $i => $line) {
$lines[] = new OrderLine($i + 1, $line->sku, $line->quantity);
}
return new self(1, $data->reference, $lines);
}
}
The generated Order.OrderInput.jsonapi schema (shortened):
"attributes": { "properties": { "reference": { "type": "string" } } },
"relationships": {
"properties": {
"lines": { "properties": { "data": { "type": "array", "items": {
"properties": { "type": { "type": "string" }, "id": { "type": "string", "format": "iri-reference" } },
"required": ["type", "id"]
} } } }
}
}
A request that follows the schema fails. The processor gets an OrderLineInput whose properties were never set:
POST /orders
Content-Type: application/vnd.api+json
{"data":{"type":"Order","attributes":{"reference":"R-9"},"relationships":{"lines":{"data":[{"type":"OrderLine","id":"/order_lines/1"}]}}}}
Error: Typed property App\ApiResource\OrderLineInput::$sku must not be accessed before initialization
A request with the lines under attributes works and answers 201, but the schema does not document it:
POST /orders
Content-Type: application/vnd.api+json
{"data":{"type":"Order","attributes":{"reference":"R-9","lines":[{"sku":"X","quantity":2}]}}}
Expected: for an input schema, the attributes/relationships split follows the input class. lines is then documented under attributes as an array of OrderLineInput, which is the shape the denormalizer reads.
Possible Solution
Use the input class (or the output class for output schemas) in getRelationship(), not the resource class.
Additional Context
Checked on api-platform/core v4.4.4, Symfony 7.4, PHP 8.5, in an empty Symfony app. The code of JsonApi\JsonSchema\SchemaFactory on main is the same as in v4.4.4.
The app has only FrameworkBundle, TwigBundle and ApiPlatformBundle, with this config:
$c->extension('api_platform', [
'mapping' => ['paths' => ['%kernel.project_dir%/src/ApiResource']],
'formats' => ['jsonapi' => ['application/vnd.api+json']],
'docs_formats' => ['jsonopenapi' => ['application/vnd.openapi+json']],
'doctrine' => ['enabled' => false],
]);
The spec comes from bin/console api:openapi:export.
- 主要语言
- PHP
- 星标
- 2.6k
- 派生
- 989
- 平均合并
- 1 天 23 小时
- 30 天内合并 PR
- 102
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
api-platform/core 的其他 Issue
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`可能已有人在做 @ousamabenyounes 于 1 天前认领。 未关闭
难度 2/5 1 小时以内 新手友好度 80/100
api-platform/core#8701 ·
维护者通常 1 天内回复
-
JSON:API: `JsonApiCollectionBaseSchemaNoPagination` declares `data` as `type: array` without `items`未关闭
难度 2/5 1-3 小时 新手友好度 82/100
api-platform/core#8699 ·
维护者通常 1 天内回复
-
api:openapi:export --spec-version=3.0.0 outputs `webhooks`, which is invalid in OpenAPI 3.0可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
api-platform/core#8690 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 85/100
api-platform/core#8680 ·
维护者通常 1 天内回复
-
难度 3/5 1-2 天 新手友好度 68/100
api-platform/core#8725 ·
维护者通常 1 天内回复
查看 api-platform/core 的全部 Issue
相似的 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 88/100
-
难度 2/5 1-3 小时 新手友好度 88/100
grokability/snipe-it#19857 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 92/100
维护者通常 1 天内回复
-
Authentication / Authorization Feature: Volunteer
难度 2/5 1-3 小时 新手友好度 85/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 64/100
WordPress/mcp-adapter#399 ·
维护者通常 1 天内回复