api-platform / api-platform/core
OpenAPI: Support for JSON-LD Only APIs
- Dominant language
- PHP
- Stars
- 2.6k
- Forks
- 980
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 48
Description
## Context
When building a JSON-LD only API (no plain JSON format), the OpenAPI specification still generates duplicate schemas and content types. Users have to create custom OpenApiFactory decorators to filter these out.
## Current Behavior
With this configuration:
```yaml
api_platform:
formats:
jsonld: ['application/ld+json']
error_formats:
jsonld: ['application/ld+json']
jsonproblem: ['application/problem+json']
```
The OpenAPI export still produces:
1. **Duplicate schemas**: `EmailMessage` AND `EmailMessage.jsonld`
2. **Multiple content types in responses**: `application/ld+json`, `application/problem+json`, `application/json`
## Workaround We Implemented
We created an `OpenApiFactory` decorator that:
### 1. Filters non-JSON-LD content types from responses
```php
private const NON_JSONLD_MIME_TYPES = [
'text/html',
'application/problem+json',
'application/json',
];
private function filterNonJsonContentTypes(PathItem $pathItem): PathItem
{
foreach (PathItem::$methods as $method) {
$operation = $pathItem->{"get" . ucfirst(strtolower($method))}();
if (null === $operation) continue;
foreach ($operation->getResponses() as $status => $response) {
$content = $response->getContent();
if ($content) {
$filteredContent = new \ArrayObject();
foreach ($content as $mimeType => $mediaType) {
if (!in_array($mimeType, self::NON_JSONLD_MIME_TYPES, true)) {
$filteredContent[$mimeType] = $mediaType;
}
}
// ... update response
}
}
}
return $pathItem;
}
```
### 2. Removes duplicate schemas (keeps only .jsonld versions)
```php
private function filterNonJsonSchemas(OpenApi $openApi): OpenApi
{
$schemas = $openApi->getComponents()->getSchemas();
// Collect base names that have .jsonld versions
$jsonldSchemas = [];
foreach ($schemas as $name => $schema) {
if (str_ends_with($name, '.jsonld')) {
$jsonldSchemas[substr($name, 0, -7)] = true;
}
}
$filteredSchemas = new \ArrayObject();
foreach ($schemas as $name => $schema) {
$baseName = preg_replace('/\.jsonld$/', '', $name);
// Skip non-.jsonld version when .jsonld exists
if (!str_ends_with($name, '.jsonld') && isset($jsonldSchemas[$baseName])) {
continue;
}
$filteredSchemas[$name] = $schema;
}
return $openApi->withComponents($components->withSchemas($filteredSchemas));
}
```
## Proposed Framework Enhancement
### Option A: `openapi.prefer_format` Configuration
Add a configuration option to specify the preferred format for OpenAPI documentation:
```yaml
api_platform:
openapi:
prefer_format: jsonld # Only show jsonld schemas and content types
```
**Implementation:**
In `OpenApiFactory`, when `prefer_format` is set:
1. Only generate schemas for that format (skip others even if configured)
2. Only include that format's content type in responses
3. Remove duplicate schemas that exist in multiple format variants
### Option B: `openapi.formats` Configuration
Allow explicit control over which formats appear in OpenAPI (separate from runtime formats):
```yaml
api_platform:
formats:
jsonld: ['application/ld+json']
json: ['application/json'] # Available at runtime
openapi:
formats:
jsonld: ['application/ld+json'] # Only this in docs
```
**Implementation:**
In `OpenApiFactory::getMimeTypes()`:
```php
private function getMimeTypes(HttpOperation $operation): array
{
// Use openapi.formats if configured, otherwise fall back to operation formats
$responseFormats = $this->openapiFormats
?? $operation->getOutputFormats()
?? [];
// ... rest of method
}
```
### Option C: Automatic Deduplication
When multiple formats produce equivalent schemas (same structure, different naming), automatically deduplicate:
```php
// In OpenApiFactory::collectPaths() or a new dedicated method
private function deduplicateSchemas(\ArrayObject $schemas): \ArrayObject
{
$dominated = [];
// jsonld dominates json (more specific)
foreach ($schemas as $name => $schema) {
if (str_ends_with($name, '.jsonld')) {
$baseName = substr($name, 0, -7);
if (isset($schemas[$baseName])) {
$dominated[$baseName] = true;
}
}
}
// Remove dominated schemas
foreach ($dominated as $name => $_) {
unset($schemas[$name]);
}
return $schemas;
}
```
## Recommendation
**Option A (`prefer_format`) is the cleanest** because:
1. Simple, single configuration option
2. Clear intent: "I want my OpenAPI docs in this format"
3. Doesn't affect runtime behavior
4. Easy to understand and document
**Example implementation location:**
- Configuration: `src/Symfony/Bundle/DependencyInjection/Configuration.php`
- Logic: `src/OpenApi/Factory/OpenApiFactory.php`
## Additional Consideration: Accept/Content-Type Headers
Our decorator also adds `Accept` and `Content-Type` header parameters to all operations, showing available formats as an enum. This is useful documentation that API Platform could generate automatically.
```yaml
parameters:
- name: Accept
in: header
schema:
type: string
enum: ['application/ld+json']
default: 'application/ld+json'
```
This could be another configuration option:
```yaml
api_platform:
openapi:
document_content_negotiation_headers: true
```
Contributor guide
Assessment
This issue has not been assessed yet.