JSON Schema: differences with symfony/json-schema
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
The issue bundles four separate schema-generation bugs: DiscriminatorMap handling, empty JSON:API properties serialized as [], enum default normalization, and DateTimeZone description. Start with the api:json-schema:generate and api:openapi:export commands and the code that builds their schemas, and treat each item as its own change. The discriminator item needs a decision on anyOf versus oneOf before anyone writes code, so it is not a good first task.
索引モデルが issue の本文から書いたものです。
説明
API Platform version(s) affected
4.4
Description
Comparing api-platform/json-schema with the upcoming symfony/json-schema component (https://github.com/symfony/symfony/pull/66685) shows the following differences.
1. #[DiscriminatorMap] is ignored
When a class uses the serializer's #[DiscriminatorMap], the generated JSON Schema (and the OpenAPI document) describes only the base class. The type property and the properties of the mapped classes are missing. Generated SDKs and MCP tools may misbehave as a result.
Code sample
#[DiscriminatorMap(typeProperty: 'type', mapping: [
'webhook' => WebhookChannelInput::class,
'email' => EmailChannelInput::class,
])]
abstract class NotificationChannelInput
{
}
final class WebhookChannelInput extends NotificationChannelInput
{
public function __construct(
public readonly string $type,
public readonly WebhookConfig $config,
) {
}
}
final class EmailChannelInput extends NotificationChannelInput
{
public function __construct(
public readonly string $type,
public readonly string $address,
) {
}
}
#[ApiResource(operations: [new Post(input: NotificationChannelInput::class)])]
final class ChannelResource
{
}
Current behaviour
bin/console api:json-schema:generate 'App\ApiResource\ChannelResource' --type=input returns ($schema line removed):
{
"$ref": "#/definitions/ChannelResource.NotificationChannelInput",
"definitions": {
"ChannelResource.NotificationChannelInput": {
"type": "object"
}
}
}
The OpenAPI export has the same empty {"type": "object"} for every format.
Behaviour of symfony/json-schema
symfony/json-schema describes the base class as an anyOf of the mapped classes. Each branch requires the type property (unless it is the defaultType), and each mapped class gets its type property as a const. anyOf is used instead of oneOf because some LLM providers reject oneOf. Applied to the sample above, following testDiscriminatorMapBecomesOneOfTheMappedClasses:
{
"$ref": "#/$defs/NotificationChannelInput",
"$defs": {
"NotificationChannelInput": {
"anyOf": [
{"$ref": "#/$defs/WebhookChannelInput", "required": ["type"]},
{"$ref": "#/$defs/EmailChannelInput", "required": ["type"]}
]
},
"WebhookChannelInput": {
"type": "object",
"properties": {
"type": {"type": "string", "const": "webhook"},
"config": {"$ref": "#/$defs/WebhookConfig"}
}
},
"EmailChannelInput": {
"type": "object",
"properties": {
"type": {"type": "string", "const": "email"},
"address": {"type": "string"}
}
}
}
}
2. JSON:API: empty properties is serialized as []
For a class without attributes, the JSON:API schema contains "properties": []. The JSON Schema specification requires properties to be an object, so this is an invalid schema.
Code sample
Same as above: NotificationChannelInput has no properties of its own.
Current behaviour
bin/console api:openapi:export contains, for ChannelResource.NotificationChannelInput.jsonapi:
{
"properties": {
"data": {
"type": "object",
"properties": {
"id": {"type": "string"},
"type": {"type": "string"},
"attributes": {
"type": "object",
"properties": []
}
},
"required": ["type", "id"]
}
}
}
In the API Platform test app, 13 JSON:API schemas have this "attributes": {"properties": []}, among them ExceptionResource.jsonapi and TooManyRequests.jsonapi.
Behaviour of symfony/json-schema
symfony/json-schema has no JSON:API support, but fixed the same bug: getRoot(), getDefinitions() and toArray() return empty schemas as \ArrayObject, so json_encode() prints {} instead of [].
3. Enum defaults are serialized as objects
Only a top-level BackedEnum default is converted to its value. An array of enums, or a UnitEnum default, is serialized as enum objects. JSON Schema recommends that default be valid against its schema; these defaults are not.
Code sample
enum BackedSuit: string
{
case Hearts = 'H';
case Spades = 'S';
}
enum UnitSuit
{
case Hearts;
case Spades;
}
#[ApiResource(operations: [new Get()])]
final class EnumArrayDefault
{
public int $id = 1;
/** @var list<BackedSuit> */
public array $backedSuits = [BackedSuit::Hearts, BackedSuit::Spades];
public UnitSuit $unitSuit = UnitSuit::Spades;
}
Current behaviour
bin/console api:openapi:export contains, for EnumArrayDefault:
"backedSuits": {
"default": [{"name": "Hearts", "value": "H"}, {"name": "Spades", "value": "S"}],
"type": "array",
"items": {"type": "string", "enum": ["H", "S"]}
},
"unitSuit": {
"default": {"name": "Spades"},
"$ref": "#/components/schemas/UnitSuit"
}
Behaviour of symfony/json-schema
symfony/json-schema normalizes defaults recursively: a BackedEnum becomes its value, a UnitEnum its name, and arrays are normalized element by element. The defaults above become ["H", "S"] and "Spades".
4. DateTimeZone is described as an object
Symfony's DateTimeZoneNormalizer serializes a DateTimeZone as a string (e.g. "Europe/Paris"), but the schema describes it as an object built from the getters of the PHP class.
Code sample
#[ApiResource(operations: [new Get()])]
final class Event
{
public int $id = 1;
public ?\DateTimeZone $timezone = null;
}
Current behaviour
bin/console api:openapi:export contains:
"timezone": {
"anyOf": [{"$ref": "#/components/schemas/DateTimeZone"}, {"type": "null"}]
}
"DateTimeZone": {
"type": "object",
"properties": {
"name": {"readOnly": true, "type": "string"},
"transitions": {"readOnly": true, "anyOf": [{"type": "array", "items": {}}, {"type": "boolean", "const": false}]},
"location": {"readOnly": true, "anyOf": [{"type": "array", "items": {}}, {"type": "boolean", "const": false}]}
}
}
The same DateTimeZone definition is repeated for each format (.graphql, .html, .jsonld, .multipart, .xml).
Behaviour of symfony/json-schema
symfony/json-schema describes DateTimeZone as {"type": "string"}.
- 主要言語
- PHP
- スター
- 2.6k
- フォーク
- 989
- 平均マージ
- 1日 23時間
- マージ済み PR(30日)
- 102
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- 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対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 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
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
opnsense/core#11052 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
needs triage
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
-
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 日以内に返信