Skip to content

Conversation

ttskch
Copy link
Contributor

@ttskch ttskch commented Sep 22, 2025

Q A
Branch? 4.2
Tickets Related to #6960
License MIT
Doc PR N/A

This PR makes @id and @type properties required only in the JSON-LD schema for output.

@ttskch ttskch force-pushed the fix/jsonschema-jsonld branch from 6ea2a72 to ff5e438 Compare September 22, 2025 14:18
@ttskch ttskch marked this pull request as draft September 22, 2025 15:17
@ttskch ttskch changed the title refactor(jsonschema/jsonld): remove unnecessary codes fix(jsonschema/jsonld): force input schema to json format instead of jsonld format Sep 22, 2025
@ttskch ttskch marked this pull request as ready for review September 22, 2025 16:01
@soyuka
Copy link
Member

soyuka commented Sep 26, 2025

Interesting, but its possible when using JSON-LD to have @id or @context (though they're not required). They'll not appear in the schema anymore from what I read but are they allowed?

@ttskch
Copy link
Contributor Author

ttskch commented Sep 26, 2025

@soyuka

its possible when using JSON-LD to have @id or @context (though they're not required)

I see. If so, when using JSON-LD, @id and @type should be required in the output schema, and they should be optional in the input schema. This means we'll need to generate two schemas like Book.jsonld.output and Book.jsonld.input (as I suggested in #6485).

Would it be ok to modify this PR like that?

@ttskch ttskch force-pushed the fix/jsonschema-jsonld branch from 834d183 to 638ad92 Compare September 26, 2025 18:52
@ttskch
Copy link
Contributor Author

ttskch commented Sep 26, 2025

like this. (Just a PoC, don't merge yet.)

@ttskch
Copy link
Contributor Author

ttskch commented Sep 26, 2025

Example of output result

1 2

openapi.json

{
    "components": {
        "schemas": {
            "Book": {
                "type": "object",
                "properties": {
                    "id": {
                        "readOnly": true,
                        "type": "integer"
                    },
                    "title": {
                        "type": "string"
                    }
                },
                "required": [
                    "title"
                ]
            },
            "Book.jsonld.input": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraItemBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Book"
                    }
                ]
            },
            "Book.jsonld.output": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraOutputBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Book"
                    }
                ]
            },
            "HydraItemBaseSchema": {
                "type": "object",
                "properties": {
                    "@context": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "@vocab": {
                                        "type": "string"
                                    },
                                    "hydra": {
                                        "type": "string",
                                        "enum": [
                                            "http://www.w3.org/ns/hydra/core#"
                                        ]
                                    }
                                },
                                "required": [
                                    "@vocab",
                                    "hydra"
                                ],
                                "additionalProperties": true
                            }
                        ]
                    },
                    "@id": {
                        "type": "string"
                    },
                    "@type": {
                        "type": "string"
                    }
                }
            },
            "HydraOutputBaseSchema": {
                "required": [
                    "@id",
                    "@type"
                ],
                "type": "object",
                "properties": {
                    "@context": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "@vocab": {
                                        "type": "string"
                                    },
                                    "hydra": {
                                        "type": "string",
                                        "enum": [
                                            "http://www.w3.org/ns/hydra/core#"
                                        ]
                                    }
                                },
                                "required": [
                                    "@vocab",
                                    "hydra"
                                ],
                                "additionalProperties": true
                            }
                        ]
                    },
                    "@id": {
                        "type": "string"
                    },
                    "@type": {
                        "type": "string"
                    }
                }
            }
        }
    }
}

Reproducer

https://github.com/ttskch/api-platform-core-7397

@soyuka
Copy link
Member

soyuka commented Sep 27, 2025

Nice this looks fine! Can the output stay Book.jsonld?

@ttskch ttskch changed the title fix(jsonschema/jsonld): force input schema to json format instead of jsonld format fix(jsonschema/jsonld): make @id and @type properties required only in the JSON-LD schema for output Sep 27, 2025
@ttskch
Copy link
Contributor Author

ttskch commented Sep 27, 2025

@soyuka I fixed in 479f46b and updated the PR title and description.

Example of output result

1 2

openapi.json

{
    "openapi": "3.1.0",
    "info": {
        "title": "Hello API Platform",
        "description": "",
        "version": "1.0.0"
    },
    "servers": [
        {
            "url": "/",
            "description": ""
        }
    ],
    "paths": {
        "/api/books": {
            "get": {
                "operationId": "api_books_get_collection",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "200": {
                        "description": "Book collection",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "type": "object",
                                    "description": "Book.jsonld collection.",
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/HydraCollectionBaseSchema"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "hydra:member": {
                                                    "type": "array",
                                                    "items": {
                                                        "$ref": "#/components/schemas/Book.jsonld"
                                                    }
                                                }
                                            }
                                        }
                                    ]
                                }
                            }
                        }
                    }
                },
                "summary": "Retrieves the collection of Book resources.",
                "description": "Retrieves the collection of Book resources.",
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "description": "The collection page number",
                        "required": false,
                        "deprecated": false,
                        "schema": {
                            "type": "integer",
                            "default": 1
                        },
                        "style": "form",
                        "explode": false
                    }
                ]
            },
            "post": {
                "operationId": "api_books_post",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "201": {
                        "description": "Book resource created",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Book.jsonld"
                                }
                            }
                        },
                        "links": {}
                    },
                    "400": {
                        "description": "Invalid input",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    },
                    "422": {
                        "description": "An error occurred",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Creates a Book resource.",
                "description": "Creates a Book resource.",
                "parameters": [],
                "requestBody": {
                    "description": "The new Book resource",
                    "content": {
                        "application/ld+json": {
                            "schema": {
                                "$ref": "#/components/schemas/Book.jsonld.input"
                            }
                        }
                    },
                    "required": true
                }
            }
        },
        "/api/books/{id}": {
            "get": {
                "operationId": "api_books_id_get",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "200": {
                        "description": "Book resource",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Book.jsonld"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Retrieves a Book resource.",
                "description": "Retrieves a Book resource.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Book identifier",
                        "required": true,
                        "deprecated": false,
                        "schema": {
                            "type": "string"
                        },
                        "style": "simple",
                        "explode": false
                    }
                ]
            },
            "delete": {
                "operationId": "api_books_id_delete",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "204": {
                        "description": "Book resource deleted"
                    },
                    "404": {
                        "description": "Not found",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Removes the Book resource.",
                "description": "Removes the Book resource.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Book identifier",
                        "required": true,
                        "deprecated": false,
                        "schema": {
                            "type": "string"
                        },
                        "style": "simple",
                        "explode": false
                    }
                ]
            },
            "patch": {
                "operationId": "api_books_id_patch",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "200": {
                        "description": "Book resource updated",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Book.jsonld"
                                }
                            }
                        },
                        "links": {}
                    },
                    "400": {
                        "description": "Invalid input",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    },
                    "422": {
                        "description": "An error occurred",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            }
                        },
                        "links": {}
                    },
                    "404": {
                        "description": "Not found",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Updates the Book resource.",
                "description": "Updates the Book resource.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Book identifier",
                        "required": true,
                        "deprecated": false,
                        "schema": {
                            "type": "string"
                        },
                        "style": "simple",
                        "explode": false
                    }
                ],
                "requestBody": {
                    "description": "The updated Book resource",
                    "content": {
                        "application/merge-patch+json": {
                            "schema": {
                                "$ref": "#/components/schemas/Book"
                            }
                        }
                    },
                    "required": true
                }
            }
        }
    },
    "components": {
        "schemas": {
            "Book": {
                "type": "object",
                "properties": {
                    "id": {
                        "readOnly": true,
                        "type": "integer"
                    },
                    "title": {
                        "type": "string"
                    }
                },
                "required": [
                    "title"
                ]
            },
            "Book.jsonld": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraOutputBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Book"
                    }
                ]
            },
            "Book.jsonld.input": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraItemBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Book"
                    }
                ]
            },
            "ConstraintViolation": {
                "type": "object",
                "description": "Unprocessable entity",
                "properties": {
                    "status": {
                        "default": 422,
                        "type": "integer"
                    },
                    "violations": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "propertyPath": {
                                    "type": "string",
                                    "description": "The property path of the violation"
                                },
                                "message": {
                                    "type": "string",
                                    "description": "The message associated with the violation"
                                }
                            }
                        }
                    },
                    "detail": {
                        "readOnly": true,
                        "type": "string"
                    },
                    "type": {
                        "readOnly": true,
                        "type": "string"
                    },
                    "title": {
                        "readOnly": true,
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "instance": {
                        "readOnly": true,
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                }
            },
            "ConstraintViolation.jsonld": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraOutputBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/ConstraintViolation"
                    }
                ]
            },
            "Error": {
                "type": "object",
                "description": "A representation of common errors.",
                "properties": {
                    "title": {
                        "readOnly": true,
                        "description": "A short, human-readable summary of the problem.",
                        "type": "string"
                    },
                    "detail": {
                        "readOnly": true,
                        "description": "A human-readable explanation specific to this occurrence of the problem.",
                        "type": "string"
                    },
                    "status": {
                        "type": "number",
                        "examples": [
                            404
                        ],
                        "default": 400
                    },
                    "instance": {
                        "readOnly": true,
                        "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.",
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "type": {
                        "readOnly": true,
                        "description": "A URI reference that identifies the problem type",
                        "type": "string"
                    }
                }
            },
            "Error.jsonld": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraOutputBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Error"
                    }
                ]
            },
            "HydraCollectionBaseSchema": {
                "type": "object",
                "required": [
                    "hydra:member"
                ],
                "properties": {
                    "hydra:member": {
                        "type": "array"
                    },
                    "hydra:totalItems": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "hydra:view": {
                        "type": "object",
                        "properties": {
                            "@id": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "@type": {
                                "type": "string"
                            },
                            "hydra:first": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "hydra:last": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "hydra:previous": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "hydra:next": {
                                "type": "string",
                                "format": "iri-reference"
                            }
                        },
                        "example": {
                            "@id": "string",
                            "type": "string",
                            "hydra:first": "string",
                            "hydra:last": "string",
                            "hydra:previous": "string",
                            "hydra:next": "string"
                        }
                    },
                    "hydra:search": {
                        "type": "object",
                        "properties": {
                            "@type": {
                                "type": "string"
                            },
                            "hydra:template": {
                                "type": "string"
                            },
                            "hydra:variableRepresentation": {
                                "type": "string"
                            },
                            "hydra:mapping": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "@type": {
                                            "type": "string"
                                        },
                                        "variable": {
                                            "type": "string"
                                        },
                                        "property": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "required": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "HydraItemBaseSchema": {
                "type": "object",
                "properties": {
                    "@context": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "@vocab": {
                                        "type": "string"
                                    },
                                    "hydra": {
                                        "type": "string",
                                        "enum": [
                                            "http://www.w3.org/ns/hydra/core#"
                                        ]
                                    }
                                },
                                "required": [
                                    "@vocab",
                                    "hydra"
                                ],
                                "additionalProperties": true
                            }
                        ]
                    },
                    "@id": {
                        "type": "string"
                    },
                    "@type": {
                        "type": "string"
                    }
                }
            },
            "HydraOutputBaseSchema": {
                "required": [
                    "@id",
                    "@type"
                ],
                "type": "object",
                "properties": {
                    "@context": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "@vocab": {
                                        "type": "string"
                                    },
                                    "hydra": {
                                        "type": "string",
                                        "enum": [
                                            "http://www.w3.org/ns/hydra/core#"
                                        ]
                                    }
                                },
                                "required": [
                                    "@vocab",
                                    "hydra"
                                ],
                                "additionalProperties": true
                            }
                        ]
                    },
                    "@id": {
                        "type": "string"
                    },
                    "@type": {
                        "type": "string"
                    }
                }
            }
        },
        "responses": {},
        "parameters": {},
        "examples": {},
        "requestBodies": {},
        "headers": {},
        "securitySchemes": {}
    },
    "security": [],
    "tags": [
        {
            "name": "Book",
            "description": "Resource 'Book' operations."
        }
    ],
    "webhooks": {}
}

Reproducer

https://github.com/ttskch/api-platform-core-7397

@ttskch
Copy link
Contributor Author

ttskch commented Sep 27, 2025

I'm still working to fix the CI error.

@ttskch ttskch force-pushed the fix/jsonschema-jsonld branch 2 times, most recently from 1cda6cf to 5fdaa77 Compare September 27, 2025 15:17
@soyuka
Copy link
Member

soyuka commented Sep 27, 2025

I can take a look if you want, and I may move this test to phpunit I thought that keeping Book.jsonld would do the trick but its probably looking for Book.jsonld-input ^^. Let me know if I should fix this.

@soyuka soyuka force-pushed the fix/jsonschema-jsonld branch from cb98a29 to b0cac3a Compare September 29, 2025 09:16
$definitionName .= '.input';
}

$jsonSchema = $this->schemaFactory->buildSchema($className, 'json', $type, $operation, new Schema(version: $schema->getVersion()), $serializerContext, $forceCollection);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't work as references will be with json instead of jsonld

Copy link
Contributor Author

@ttskch ttskch Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka What do you mean?🤔 I understand that the intention of your fix in #6960 was to reference a json schema in the definition of a jsonld schema. I completely agree with that intention, but unfortunately #6960 had the following bugs:

  • The output schema was applied even on input, making @id and @type required.
  • The json schema was not referenced (only HydraItemBaseSchema and HydraCollectionBaseSchema were referenced).

This PR fixes these two bugs.

In order to reference or embed a json schema (e.g., Book instead of Book.jsonld), I believe you need to explicitly create a json schema here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then relations references are not correct anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka Sorry, I don't understand what you're saying. Could you please explain in more detail? 🙇‍♂️

Copy link
Member

@soyuka soyuka Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use:

php -d memory_limit=-1 tests/Fixtures/app/console api:json-schema:generate 'ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5501\BrokenDocs' --format=jsonld --type=output --operation=_api_/broken_docs/{id}{._format}_get

The current output:

 {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$ref": "#/definitions/BrokenDocs.jsonld-location.read_collection",
    "definitions": {
        "BrokenDocs.jsonld-location.read_collection": {
            "allOf": [
                {
                    "$ref": "#/definitions/HydraItemBaseSchema"
                },
                {
                    "type": "object",
                    "properties": {
                        "locations": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Related.jsonld-location.read_collection"
                            }
                        }
                    }
                }
            ]
        },
        "Related.jsonld-location.read_collection": {
            "allOf": [
                {
                    "$ref": "#/definitions/HydraItemBaseSchema"
                },
                {
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": [
                                "string",
                                "null"
                            ]
                        }
                    }
                }
            ]
        },
        "HydraItemBaseSchema": {
            "required": [
                "@id",
                "@type"
            ],
            "type": "object",
            "properties": {
                "@context": {
                    "oneOf": [
                        {
                            "type": "string"
                        },
                        {
                            "type": "object",
                            "properties": {
                                "@vocab": {
                                    "type": "string"
                                },
                                "hydra": {
                                    "type": "string",
                                    "enum": [
                                        "http://www.w3.org/ns/hydra/core#"
                                    ]
                                }
                            },
                            "required": [
                                "@vocab",
                                "hydra"
                            ],
                            "additionalProperties": true
                        }
                    ]
                },
                "@id": {
                    "type": "string"
                },
                "@type": {
                    "type": "string"
                }
            }
        }
    }
}

With this PR:

 {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$ref": "#/definitions/BrokenDocs.jsonld-location.read_collection",
    "definitions": {
        "BrokenDocs.jsonld-location.read_collection": {
            "allOf": [
                {
                    "$ref": "#/definitions/HydraOutputBaseSchema"
                },
                {
                    "type": "object",
                    "properties": {
                        "locations": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Related-location.read_collection"
                            }
                        }
                    }
                }
            ]
        },
        "Related.jsonld-location.read_collection": {
            "allOf": [
                {
                    "$ref": "#/definitions/HydraOutputBaseSchema"
                },
                {
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": [
                                "string",
                                "null"
                            ]
                        }
                    }
                }
            ]
        },
        "HydraOutputBaseSchema": {
            "required": [
                "@id",
                "@type"
            ],
            "type": "object",
            "properties": {
                "@context": {
                    "oneOf": [
                        {
                            "type": "string"
                        },
                        {
                            "type": "object",
                            "properties": {
                                "@vocab": {
                                    "type": "string"
                                },
                                "hydra": {
                                    "type": "string",
                                    "enum": [
                                        "http://www.w3.org/ns/hydra/core#"
                                    ]
                                }
                            },
                            "required": [
                                "@vocab",
                                "hydra"
                            ],
                            "additionalProperties": true
                        }
                    ]
                },
                "@id": {
                    "type": "string"
                },
                "@type": {
                    "type": "string"
                }
            }
        }
    }
}

See the $ref should target the jsonld relation not the normal one.

Copy link
Contributor Author

@ttskch ttskch Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka Thank you for the details! I understand now.

It's true that the problem is that there is no definition of Related-location.read_collection in this JSON schema for "$ref": "#/definitions/Related-location.read_collection".

I tried to fix it with 69d0851.

php -d memory_limit=-1 tests/Fixtures/app/console api:json-schema:generate 'ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5501\BrokenDocs' --format=jsonld --type=output --operation=_api_/broken_docs/{id}{._format}_get

The new output for this is as follows:

 {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$ref": "#/definitions/BrokenDocs.jsonld-location.read_collection",
    "definitions": {
        "BrokenDocs.jsonld-location.read_collection": {
            "allOf": [
                {
                    "$ref": "#/definitions/HydraOutputBaseSchema"
                },
                {
                    "$ref": "#/definitions/BrokenDocs-location.read_collection"
                }
            ]
        },
        "Related.jsonld-location.read_collection": {
            "allOf": [
                {
                    "$ref": "#/definitions/HydraOutputBaseSchema"
                },
                {
                    "$ref": "#/definitions/Related-location.read_collection"
                }
            ]
        },
        "HydraOutputBaseSchema": {
            "required": [
                "@id",
                "@type"
            ],
            "type": "object",
            "properties": {
                "@context": {
                    "oneOf": [
                        {
                            "type": "string"
                        },
                        {
                            "type": "object",
                            "properties": {
                                "@vocab": {
                                    "type": "string"
                                },
                                "hydra": {
                                    "type": "string",
                                    "enum": [
                                        "http://www.w3.org/ns/hydra/core#"
                                    ]
                                }
                            },
                            "required": [
                                "@vocab",
                                "hydra"
                            ],
                            "additionalProperties": true
                        }
                    ]
                },
                "@id": {
                    "type": "string"
                },
                "@type": {
                    "type": "string"
                }
            }
        },
        "Related-location.read_collection": {
            "type": "object",
            "properties": {
                "name": {
                    "type": [
                        "string",
                        "null"
                    ]
                }
            }
        },
        "BrokenDocs-location.read_collection": {
            "type": "object",
            "properties": {
                "locations": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/Related-location.read_collection"
                    }
                }
            }
        }
    }
}

Also, the openapi.json of https://github.com/ttskch/api-platform-core-7397 is as follows:

{
    "openapi": "3.1.0",
    "info": {
        "title": "Hello API Platform",
        "description": "",
        "version": "1.0.0"
    },
    "servers": [
        {
            "url": "/",
            "description": ""
        }
    ],
    "paths": {
        "/api/books": {
            "get": {
                "operationId": "api_books_get_collection",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "200": {
                        "description": "Book collection",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "type": "object",
                                    "description": "Book.jsonld collection.",
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/HydraCollectionBaseSchema"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "hydra:member": {
                                                    "type": "array",
                                                    "items": {
                                                        "$ref": "#/components/schemas/Book.jsonld"
                                                    }
                                                }
                                            }
                                        }
                                    ]
                                }
                            }
                        }
                    }
                },
                "summary": "Retrieves the collection of Book resources.",
                "description": "Retrieves the collection of Book resources.",
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "description": "The collection page number",
                        "required": false,
                        "deprecated": false,
                        "schema": {
                            "type": "integer",
                            "default": 1
                        },
                        "style": "form",
                        "explode": false
                    }
                ]
            },
            "post": {
                "operationId": "api_books_post",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "201": {
                        "description": "Book resource created",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Book.jsonld"
                                }
                            }
                        },
                        "links": {}
                    },
                    "400": {
                        "description": "Invalid input",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    },
                    "422": {
                        "description": "An error occurred",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Creates a Book resource.",
                "description": "Creates a Book resource.",
                "parameters": [],
                "requestBody": {
                    "description": "The new Book resource",
                    "content": {
                        "application/ld+json": {
                            "schema": {
                                "$ref": "#/components/schemas/Book.jsonld.input"
                            }
                        }
                    },
                    "required": true
                }
            }
        },
        "/api/books/{id}": {
            "get": {
                "operationId": "api_books_id_get",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "200": {
                        "description": "Book resource",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Book.jsonld"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Retrieves a Book resource.",
                "description": "Retrieves a Book resource.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Book identifier",
                        "required": true,
                        "deprecated": false,
                        "schema": {
                            "type": "string"
                        },
                        "style": "simple",
                        "explode": false
                    }
                ]
            },
            "delete": {
                "operationId": "api_books_id_delete",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "204": {
                        "description": "Book resource deleted"
                    },
                    "404": {
                        "description": "Not found",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Removes the Book resource.",
                "description": "Removes the Book resource.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Book identifier",
                        "required": true,
                        "deprecated": false,
                        "schema": {
                            "type": "string"
                        },
                        "style": "simple",
                        "explode": false
                    }
                ]
            },
            "patch": {
                "operationId": "api_books_id_patch",
                "tags": [
                    "Book"
                ],
                "responses": {
                    "200": {
                        "description": "Book resource updated",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Book.jsonld"
                                }
                            }
                        },
                        "links": {}
                    },
                    "400": {
                        "description": "Invalid input",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    },
                    "422": {
                        "description": "An error occurred",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConstraintViolation"
                                }
                            }
                        },
                        "links": {}
                    },
                    "404": {
                        "description": "Not found",
                        "content": {
                            "application/ld+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error.jsonld"
                                }
                            },
                            "application/problem+json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "links": {}
                    }
                },
                "summary": "Updates the Book resource.",
                "description": "Updates the Book resource.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Book identifier",
                        "required": true,
                        "deprecated": false,
                        "schema": {
                            "type": "string"
                        },
                        "style": "simple",
                        "explode": false
                    }
                ],
                "requestBody": {
                    "description": "The updated Book resource",
                    "content": {
                        "application/merge-patch+json": {
                            "schema": {
                                "$ref": "#/components/schemas/Book"
                            }
                        }
                    },
                    "required": true
                }
            }
        }
    },
    "components": {
        "schemas": {
            "Book": {
                "type": "object",
                "properties": {
                    "id": {
                        "readOnly": true,
                        "type": "integer"
                    },
                    "title": {
                        "type": "string"
                    }
                },
                "required": [
                    "title"
                ]
            },
            "Book.jsonld": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraOutputBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Book"
                    }
                ]
            },
            "Book.jsonld.input": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraItemBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Book"
                    }
                ]
            },
            "ConstraintViolation": {
                "type": "object",
                "description": "Unprocessable entity",
                "properties": {
                    "status": {
                        "default": 422,
                        "type": "integer"
                    },
                    "violations": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "propertyPath": {
                                    "type": "string",
                                    "description": "The property path of the violation"
                                },
                                "message": {
                                    "type": "string",
                                    "description": "The message associated with the violation"
                                }
                            }
                        }
                    },
                    "detail": {
                        "readOnly": true,
                        "type": "string"
                    },
                    "description": {
                        "readOnly": true,
                        "type": "string"
                    },
                    "type": {
                        "readOnly": true,
                        "type": "string"
                    },
                    "title": {
                        "readOnly": true,
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "instance": {
                        "readOnly": true,
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                }
            },
            "ConstraintViolation.jsonld": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraOutputBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/ConstraintViolation"
                    }
                ],
                "description": "Unprocessable entity"
            },
            "Error": {
                "type": "object",
                "description": "A representation of common errors.",
                "properties": {
                    "title": {
                        "readOnly": true,
                        "description": "A short, human-readable summary of the problem.",
                        "type": "string"
                    },
                    "detail": {
                        "readOnly": true,
                        "description": "A human-readable explanation specific to this occurrence of the problem.",
                        "type": "string"
                    },
                    "status": {
                        "type": "number",
                        "examples": [
                            404
                        ],
                        "default": 400
                    },
                    "instance": {
                        "readOnly": true,
                        "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.",
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "type": {
                        "readOnly": true,
                        "description": "A URI reference that identifies the problem type",
                        "type": "string"
                    },
                    "description": {
                        "readOnly": true,
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                }
            },
            "Error.jsonld": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/HydraOutputBaseSchema"
                    },
                    {
                        "$ref": "#/components/schemas/Error"
                    }
                ],
                "description": "A representation of common errors."
            },
            "HydraCollectionBaseSchema": {
                "type": "object",
                "required": [
                    "hydra:member"
                ],
                "properties": {
                    "hydra:member": {
                        "type": "array"
                    },
                    "hydra:totalItems": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "hydra:view": {
                        "type": "object",
                        "properties": {
                            "@id": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "@type": {
                                "type": "string"
                            },
                            "hydra:first": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "hydra:last": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "hydra:previous": {
                                "type": "string",
                                "format": "iri-reference"
                            },
                            "hydra:next": {
                                "type": "string",
                                "format": "iri-reference"
                            }
                        },
                        "example": {
                            "@id": "string",
                            "type": "string",
                            "hydra:first": "string",
                            "hydra:last": "string",
                            "hydra:previous": "string",
                            "hydra:next": "string"
                        }
                    },
                    "hydra:search": {
                        "type": "object",
                        "properties": {
                            "@type": {
                                "type": "string"
                            },
                            "hydra:template": {
                                "type": "string"
                            },
                            "hydra:variableRepresentation": {
                                "type": "string"
                            },
                            "hydra:mapping": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "@type": {
                                            "type": "string"
                                        },
                                        "variable": {
                                            "type": "string"
                                        },
                                        "property": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "required": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "HydraItemBaseSchema": {
                "type": "object",
                "properties": {
                    "@context": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "@vocab": {
                                        "type": "string"
                                    },
                                    "hydra": {
                                        "type": "string",
                                        "enum": [
                                            "http://www.w3.org/ns/hydra/core#"
                                        ]
                                    }
                                },
                                "required": [
                                    "@vocab",
                                    "hydra"
                                ],
                                "additionalProperties": true
                            }
                        ]
                    },
                    "@id": {
                        "type": "string"
                    },
                    "@type": {
                        "type": "string"
                    }
                }
            },
            "HydraOutputBaseSchema": {
                "required": [
                    "@id",
                    "@type"
                ],
                "type": "object",
                "properties": {
                    "@context": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "object",
                                "properties": {
                                    "@vocab": {
                                        "type": "string"
                                    },
                                    "hydra": {
                                        "type": "string",
                                        "enum": [
                                            "http://www.w3.org/ns/hydra/core#"
                                        ]
                                    }
                                },
                                "required": [
                                    "@vocab",
                                    "hydra"
                                ],
                                "additionalProperties": true
                            }
                        ]
                    },
                    "@id": {
                        "type": "string"
                    },
                    "@type": {
                        "type": "string"
                    }
                }
            }
        },
        "responses": {},
        "parameters": {},
        "examples": {},
        "requestBodies": {},
        "headers": {},
        "securitySchemes": {}
    },
    "security": [],
    "tags": [
        {
            "name": "Book",
            "description": "Resource 'Book' operations."
        }
    ],
    "webhooks": {}
}

What about this behavior?

If this still causes problems, we'll give up on reusing the json schema within the jsonld schema and revert to the previous behavior for this part.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it is a really hard problem to fix I spent quite some time in the refactoring trying not to break this :/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka

I see. It's true that this change has a large impact, but the benefits are limited (just the size of openapi.json will be slightly smaller).

Therefore, the idea of ​​reusing JSON Schema will not be included in this PR. Thanks.

@soyuka soyuka force-pushed the fix/jsonschema-jsonld branch from b0cac3a to 5fdaa77 Compare September 29, 2025 09:32
public function buildSchema(string $className, string $format = 'jsonld', string $type = Schema::TYPE_OUTPUT, ?Operation $operation = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema
{
if ('jsonld' !== $format || 'input' === $type) {
if ('jsonld' !== $format) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm quite unsure about this as when its an input we were going through this path making @id required only on output

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka If we add 'input' === $type here, this method will not be executed at all during input, and Book.jsonld.input will not be generated.

The @id @type @context properties will be added during both input and output, and @id @type should only be required during output. This control is done here and here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand, this is exactly what we want: input should not have any of the json-ld specific items which is the case right now no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka

Sorry, I don't think I understand your intention correctly.

Interesting, but it's possible when using JSON-LD to have @id or @context (though they're not required).

Isn't this an argument that @id and @context should be included as optional fields in the JSON-LD input schema?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed but as we don't have "additionalProperties": false it is allowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka Ah, I understood!

By default, JSON Schema doesn't include "additionalProperties": false, so even if @id and @context aren't explicitly defined in the input schema, @id and @context can actually be included in the input, right?

If that's the case, then there's no need to create an input schema like Book.jsonld.input in the first place; the input schema can simply be the Book json schema, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly !

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soyuka Got it! I'm fixing the PR accordingly👍

@ttskch ttskch force-pushed the fix/jsonschema-jsonld branch from 53e1e96 to fa1305f Compare September 30, 2025 02:17
…ferencing it

It cannot always be referenced, as there may be resources that don't have a JSON schema but only a JSON-LD schema.
It's not certain at the time the JSON-LD schema is being generated whether a JSON schema will ultimately be defined.
Therefore, the JSON schema will be referenced only if it's already defined.
@ttskch ttskch force-pushed the fix/jsonschema-jsonld branch from fa1305f to ad9be8a Compare September 30, 2025 02:25
@ttskch
Copy link
Contributor Author

ttskch commented Sep 30, 2025

rebased with current 4.2

@ttskch ttskch force-pushed the fix/jsonschema-jsonld branch from e92aeff to d1e60d9 Compare September 30, 2025 04:22
@ttskch
Copy link
Contributor Author

ttskch commented Sep 30, 2025

@soyuka I've mostly revised the existing tests to match the implementation. The only thing I'm unsure of is how to properly modify testSchemaIsNotValid() and testSchemaIsValid() in JsonSchemaTest.

The new schema uses $ref references, so I don't know how to properly handle this in jsonrainbow/json-schema. I tried to follow the README, but it didn't work😓 Do you have any ideas?

@soyuka
Copy link
Member

soyuka commented Oct 6, 2025

can you check my patch?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants