Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 215 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![Total Downloads](https://poser.pugx.org/spiral/json-schema-generator/downloads)](https://packagist.org/packages/spiral/json-schema-generator)
[![psalm-level](https://shepherd.dev/github/spiral/json-schema-generator/level.svg)](https://shepherd.dev/github/spiral/json-schema-generator)

The JSON Schema Generator is a PHP package that simplifies the generation of [JSON schemas](https://json-schema.org/) from Data Transfer Object (DTO) classes.
The JSON Schema Generator is a PHP package that simplifies the generation of [JSON schemas](https://json-schema.org/) from Data Transfer Object (DTO) classes.
It supports PHP enumerations and generic type annotations for arrays and provides an attribute for specifying title, description, and default value.

Main use case - structured output definition for LLMs.
Expand Down Expand Up @@ -107,17 +107,34 @@ Example array output:
'description' => [
'title' => 'Description',
'description' => 'The description of the movie',
'type' => 'string',
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
],
'director' => [
'type' => 'string',
'director' => [
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
],
'releaseStatus' => [
'title' => 'Release Status',
'description' => 'The release status of the movie',
'allOf' => [
'oneOf' => [
[
'$ref' => '#/definitions/ReleaseStatus',
'type' => 'null',
],
[
'type' => 'string',
'enum' => [
'Released',
'Rumored',
'Post Production',
'In Production',
'Planned',
'Canceled',
],
],
],
],
Expand All @@ -126,20 +143,6 @@ Example array output:
'title',
'year',
],
'definitions' => [
'ReleaseStatus' => [
'title' => 'ReleaseStatus',
'type' => 'string',
'enum' => [
'Released',
'Rumored',
'Post Production',
'In Production',
'Planned',
'Canceled',
],
],
],
];
```

Expand All @@ -159,7 +162,8 @@ final class Actor
/**
* @var array<Movie>
*/
public readonly array $movies = [],
public readonly ?array $movies = null,
public readonly ?Movie $bestMovie = null;
) {
}
}
Expand Down Expand Up @@ -191,11 +195,29 @@ Example array output:
'type' => 'string',
],
'movies' => [
'type' => 'array',
'items' => [
'$ref' => '#/definitions/Movie',
'oneOf' => [
[
'type' => 'null',
],
[
'type' => 'array',
'items' => [
'$ref' => '#/definitions/Movie',
],
],
],
],
'bestMovie' => [
'title' => 'Best Movie',
'description' => 'The best movie of the actor',
'oneOf' => [
[
'type' => 'null',
],
[
'$ref' => '#/definitions/Movie',
],
],
'default' => [],
],
],
'required' => [
Expand All @@ -219,41 +241,192 @@ Example array output:
'description' => [
'title' => 'Description',
'description' => 'The description of the movie',
'type' => 'string',
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
],
'director' => [
'type' => 'string',
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
],
'releaseStatus' => [
'title' => 'Release Status',
'description' => 'The release status of the movie',
'allOf' => [
[
'$ref' => '#/definitions/ReleaseStatus',
],
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
'enum' => ['Released', 'Rumored', 'Post Production', 'In Production', 'Planned', 'Canceled'],
],
],
'required' => [
'title',
'year',
],
],
'ReleaseStatus' => [
'title' => 'ReleaseStatus',
'type' => 'string',
'enum' => [
'Released',
'Rumored',
'Post Production',
'In Production',
'Planned',
'Canceled',
],
];
```

## Polymorphic Arrays (anyOf)
The generator also supports arrays that contain different types of DTOs using PHPDoc annotations like **@var list<Movie|Series>**.
For example, if an actor can have a filmography that includes both movies and TV series, you can define it like this:
```php
namespace App\DTO;

use Spiral\JsonSchemaGenerator\Attribute\Field;

final class Actor
{
public function __construct(
public readonly string $name,
public readonly int $age,

#[Field(title: 'Biography', description: 'The biography of the actor')]
public readonly ?string $bio = null,

/**
* @var list<Movie|Series>|null
*/
#[Field(title: 'Filmography', description: 'List of movies and series featuring the actor')]
public readonly ?array $filmography = null,

#[Field(title: 'Best Movie', description: 'The best movie of the actor')]
public readonly ?Movie $bestMovie = null,

#[Field(title: 'Best Series', description: 'The most prominent series of the actor')]
public readonly ?Series $bestSeries = null,
) {}
}
```
The generated schema will reflect this with an anyOf definition in the items section:
```php
[
'properties' => [
'filmography' => [
'title' => 'Filmography',
'description' => 'List of movies and series featuring the actor',
'oneOf' => [
['type' => 'null'],
[
'type' => 'array',
'items' => [
'anyOf' => [
['$ref' => '#/definitions/Movie'],
['$ref' => '#/definitions/Series'],
],
],
],
],
]
],
],
'definitions' => [
'Movie' => [/* ... */],
'Series' => [/* ... */],
],
];
```
## Example DTO: Series
Here's what the Series class might look like:
```php
namespace App\DTO;

use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Attribute\Format;

final class Series
{
public function __construct(
#[Field(title: 'Title', description: 'The title of the series')]
public readonly string $title,

#[Field(title: 'First Air Year', description: 'The year the series first aired')]
public readonly int $firstAirYear,

#[Field(title: 'Description', description: 'The description of the series')]
public readonly ?string $description = null,

#[Field(title: 'Creator', description: 'The creator or showrunner of the series')]
public readonly ?string $creator = null,

#[Field(title: 'Series Status', description: 'The current status of the series')]
public readonly ?SeriesStatus $status = null,

#[Field(title: 'First Air Date', description: 'The original release date of the series', format: Format::Date)]
public readonly ?string $firstAirDate = null,

#[Field(title: 'Last Air Date', description: 'The most recent air date of the series', format: Format::Date)]
public readonly ?string $lastAirDate = null,

#[Field(title: 'Seasons', description: 'Number of seasons released')]
public readonly ?int $seasons = null,
) {}
}
```
> **Note**
> When using polymorphic arrays, make sure all referenced DTOs (e.g., Movie, Series)
> are also annotated properly so their definitions can be generated correctly.

## Union Types
The JSON Schema Generator supports native PHP union types (introduced in PHP 8.0), including nullable and multi-type definitions.
Here's an example DTO using union types:
```php
namespace App\DTO;

use Spiral\JsonSchemaGenerator\Attribute\Field;

final class FlexibleValue
{
public function __construct(
#[Field(title: 'Value', description: 'Can be either string or integer')]
public readonly string|int $value,

#[Field(title: 'Optional Flag', description: 'Boolean or null')]
public readonly bool|null $flag = null,

#[Field(title: 'Flexible Field', description: 'Can be string, int, or null')]
public readonly string|int|null $flex = null,
) {}
}
```
The generated schema will include a `oneOf` section to reflect the union types:
```php
[
'properties' => [
'value' => [
'title' => 'Value',
'description' => 'Can be either string or integer',
'oneOf' => [
['type' => 'string'],
['type' => 'integer'],
],
],
'flag' => [
'title' => 'Optional Flag',
'description' => 'Boolean or null',
'oneOf' => [
['type' => 'null'],
['type' => 'boolean'],
],
],
'flex' => [
'title' => 'Flexible Field',
'description' => 'Can be string, int, or null',
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
['type' => 'integer'],
],
],
],
'required' => ['value'],
]
```
> **Note**
> All supported types are automatically resolved from native PHP type declarations and reflected in the JSON Schema output using oneOf.

## Testing

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
],
"require": {
"php": ">=8.1",
"symfony/property-info": "^6.4.18 || ^7.2",
"symfony/property-info": "^6.4.18 || ^7.2.0",
"phpstan/phpdoc-parser": "^1.33 | ^2.1",
"phpdocumentor/reflection-docblock": "^5.3"
},
Expand Down
Loading
Loading