PHP code example of spiral / json-schema-generator
1. Go to this page and download the library: Download spiral/json-schema-generator library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
spiral / json-schema-generator example snippets
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
class Movie
{
public function __construct(
#[Field(title: 'Title', description: 'The title of the movie')]
public readonly string $title,
#[Field(title: 'Year', description: 'The year of the movie')]
public readonly int $year,
#[Field(title: 'Description', description: 'The description of the movie')]
public readonly ?string $description = null,
public readonly ?string $director = null,
#[Field(title: 'Release Status', description: 'The release status of the movie')]
public readonly ?ReleaseStatus $releaseStatus = null,
) {
}
}
enum ReleaseStatus: string
{
case Released = 'Released';
case Rumored = 'Rumored';
case PostProduction = 'Post Production';
case InProduction = 'In Production';
case Planned = 'Planned';
case Canceled = 'Canceled';
}
use Spiral\JsonSchemaGenerator\Generator;
use App\DTO\Movie;
$generator = new Generator();
$schema = $generator->generate(Movie::class);
// Convert to JSON
$jsonSchema = json_encode($schema, JSON_PRETTY_PRINT);
// Or use as array
$arraySchema = $schema->jsonSerialize();
[
'properties' => [
'title' => [
'title' => 'Title',
'description' => 'The title of the movie',
'type' => 'string',
],
'year' => [
'title' => 'Year',
'description' => 'The year of the movie',
'type' => 'integer',
],
'description' => [
'title' => 'Description',
'description' => 'The description of the movie',
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
],
'director' => [
'oneOf' => [
['type' => 'null'],
['type' => 'string'],
],
],
'releaseStatus' => [
'title' => 'Release Status',
'description' => 'The release status of the movie',
'oneOf' => [
[
'type' => 'null',
],
[
'type' => 'string',
'enum' => [
'Released',
'Rumored',
'Post Production',
'In Production',
'Planned',
'Canceled',
],
],
],
],
],
'
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
final class Actor
{
public function __construct(
public readonly string $name,
/**
* @var array<Movie>
*/
public readonly ?array $movies = null,
#[Field(title: 'Best Movie', description: 'The best movie of the actor')]
public readonly ?Movie $bestMovie = null,
) {
}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
final class Actor
{
public function __construct(
public readonly string $name,
/**
* @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,
) {}
}
[
'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' => [/* Movie schema definition */],
'Series' => [/* Series schema definition */],
],
];
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Schema\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,
) {}
}
enum SeriesStatus: string
{
case Running = 'Running';
case Ended = 'Ended';
case Canceled = 'Canceled';
case OnHiatus = 'On Hiatus';
}
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,
) {}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Attribute\Constraint\Pattern;
use Spiral\JsonSchemaGenerator\Attribute\Constraint\Length;
use Spiral\JsonSchemaGenerator\Schema\Format;
final readonly class User
{
public function __construct(
#[Field(title: 'Full Name', description: 'User full name in Title Case')]
#[Pattern('^[A-Z][a-z]+(?: [A-Z][a-z]+)*$')]
#[Length(min: 2, max: 100)]
public string $name,
#[Field(title: 'Username')]
#[Pattern('^[a-zA-Z0-9_]{3,20}$')]
#[Length(min: 3, max: 20)]
public string $username,
#[Field(title: 'Email', format: Format::Email)]
#[Pattern('^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')]
public string $email,
) {}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Attribute\Constraint\Range;
use Spiral\JsonSchemaGenerator\Attribute\Constraint\MultipleOf;
final readonly class Product
{
public function __construct(
#[Field(title: 'Price', description: 'Product price in USD')]
#[Range(min: 0.01, max: 99999.99)]
#[MultipleOf(0.01)]
public float $price,
#[Field(title: 'Stock Quantity')]
#[Range(min: 0, max: 10000)]
public int $stock,
#[Field(title: 'Discount Percentage')]
#[Range(min: 0, max: 100, exclusiveMax: true)]
public float $discountPercent,
) {}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Attribute\Constraint\Items;
use Spiral\JsonSchemaGenerator\Attribute\Constraint\Length;
use Spiral\JsonSchemaGenerator\Attribute\Constraint\Enum;
final readonly class BlogPost
{
public function __construct(
#[Field(title: 'Tags', description: 'Post tags')]
#[Items(min: 1, max: 10, unique: true)]
public array $tags,
#[Field(title: 'Categories', description: 'Post categories')]
#[Length(min: 1, max: 5)]
public array $categories,
#[Field(title: 'Status')]
#[Enum(['draft', 'published', 'archived', 'pending'])]
public string $status,
#[Field(title: 'Priority')]
#[Enum([1, 2, 3, 4, 5])]
public int $priority,
) {}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
final class ValidatedUser
{
public function __construct(
#[Field(title: 'Name', description: 'User full name')]
/** @var non-empty-string */
public readonly string $name,
#[Field(title: 'Age', description: 'User age')]
/** @var positive-int */
public readonly int $age,
#[Field(title: 'Score', description: 'User score between 0 and 100')]
/** @var int<0, 100> */
public readonly int $score,
#[Field(title: 'Email', description: 'User email address')]
/** @var non-empty-string */
public readonly string $email,
#[Field(title: 'Phone Number', description: 'Numeric phone number')]
/** @var numeric-string */
public readonly string $phone,
#[Field(title: 'Tags', description: 'User tags')]
/** @var non-empty-array<string> */
public readonly array $tags = [],
#[Field(title: 'Preferences', description: 'User preferences')]
/** @var array{theme: string, notifications: bool} */
public readonly array $preferences = [],
) {}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Schema\Format;
final class ContactInfo
{
public function __construct(
#[Field(title: 'Email', description: 'User email address', format: Format::Email)]
public readonly string $email,
#[Field(title: 'Website', description: 'Personal website', format: Format::Uri)]
public readonly ?string $website = null,
#[Field(title: 'Birth Date', description: 'Date of birth', format: Format::Date)]
public readonly ?string $birthDate = null,
#[Field(title: 'Last Login', description: 'Last login timestamp', format: Format::DateTime)]
public readonly ?string $lastLogin = null,
) {}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Attribute\AdditionalProperties;
final class DynamicConfig
{
public function __construct(
#[Field(title: 'Config Name', description: 'Name of the configuration set')]
public readonly string $name,
#[Field(title: 'Version', description: 'Configuration version')]
public readonly int $version,
/**
* Dynamic settings map that can contain any string values
*/
#[Field(title: 'Settings', description: 'Dynamic configuration settings')]
#[AdditionalProperties(valueType: 'string')]
public readonly array $settings = [],
/**
* Dynamic metadata with nested ValueObject instances
*/
#[Field(title: 'Metadata', description: 'Dynamic configuration metadata')]
#[AdditionalProperties(valueType: 'object', valueClass: ValueObject::class)]
public readonly array $metadata = [],
) {}
}
final class ValueObject
{
public function __construct(
public readonly string $label,
public readonly mixed $value,
) {}
}
namespace App\DTO;
use Spiral\JsonSchemaGenerator\Attribute\Field;
use Spiral\JsonSchemaGenerator\Attribute\AdditionalProperties;
final class ApiResponse
{
public function __construct(
public readonly bool $success,
#[Field(title: 'Data', description: 'API response data with any structure')]
#[AdditionalProperties(valueType: 'mixed')]
public readonly array $data = [],
#[Field(title: 'Errors', description: 'Error messages by field name')]
#[AdditionalProperties(valueType: 'string')]
public readonly array $errors = [],
#[Field(title: 'Meta', description: 'Response metadata')]
#[AdditionalProperties(valueType: 'object', valueClass: MetaValue::class)]
public readonly array $meta = [],
) {}
}
use Spiral\JsonSchemaGenerator\Generator;
use Spiral\JsonSchemaGenerator\Validation\AttributeConstraintExtractor;
use Spiral\JsonSchemaGenerator\Validation\PhpDocValidationConstraintExtractor;
use Spiral\JsonSchemaGenerator\Validation\CompositePropertyDataExtractor;
use Spiral\JsonSchemaGenerator\Validation\AdditionalPropertiesExtractor;
// Use default extractors (recommended for most cases)
$generator = new Generator(
propertyDataExtractor: CompositePropertyDataExtractor::createDefault(),
);
// Advanced configuration - custom property data extractors
$compositeExtractor = new CompositePropertyDataExtractor([
new PhpDocValidationConstraintExtractor(),
new AttributeConstraintExtractor(),
new AdditionalPropertiesExtractor(),
]);
$generator = new Generator(
propertyDataExtractor: $compositeExtractor,
);
// Use only PHPDoc constraints
$generator = new Generator(propertyDataExtractor: new CompositePropertyDataExtractor([
new PhpDocValidationConstraintExtractor(),
]));
// Use only attribute constraints
$generator = new Generator(propertyDataExtractor: new CompositePropertyDataExtractor([
new AttributeConstraintExtractor(),
]));
// Use both (default behavior)
$generator = new Generator(propertyDataExtractor: CompositePropertyDataExtractor::createDefault());
// Disable all validation constraints for performance
$generator = new Generator(propertyDataExtractor: new CompositePropertyDataExtractor([]));
use Spiral\JsonSchemaGenerator\Validation\PropertyDataExtractorInterface;
use Spiral\JsonSchemaGenerator\Parser\PropertyInterface;
use Spiral\JsonSchemaGenerator\Schema\Type;
class CustomConstraintExtractor implements PropertyDataExtractorInterface
{
public function extractValidationRules(PropertyInterface $property, Type $jsonSchemaType): array
{
$rules = [];
// Your custom constraint extraction logic here
// For example, extract constraints from custom attributes or naming conventions
return $rules;
}
}
// Use your custom extractor
$generator = new Generator(
propertyDataExtractor: CompositePropertyDataExtractor::createDefault()
->withExtractor(new CustomConstraintExtractor())
);
declare(strict_types=1);
namespace App;
use CuyZ\Valinor\Mapper\TreeMapper;
use Spiral\JsonSchemaGenerator\Generator;
final readonly class SchemaMapper
{
public function __construct(
private Generator $generator,
private TreeMapper $mapper,
) {}
public function toJsonSchema(string $class): array
{
if (\json_validate($class)) {
return \json_decode($class, associative: true);
}
if (\class_exists($class)) {
return $this->generator->generate($class)->jsonSerialize();
}
throw new \InvalidArgumentException(\sprintf('Invalid class or JSON schema provided: %s', $class));
}
/**
* @template T of object
* @param class-string<T>|null $class
* @return T
*/
public function toObject(string $json, ?string $class = null): object
{
if ($class === null) {
return \json_decode($json, associative: false);
}
return $this->mapper->map($class, \json_decode($json, associative: true));
}
}
use CuyZ\Valinor\MapperBuilder;
use Spiral\JsonSchemaGenerator\Generator;
// Set up the mapper with flexible casting and permissive types
$treeMapper = (new MapperBuilder())
->enableFlexibleCasting()
->allowPermissiveTypes()
->build();
$mapper = new SchemaMapper(
generator: new Generator(),
mapper: $treeMapper
);
// Generate JSON schema for your DTO
$schema = $mapper->toJsonSchema(ValidatedUser::class);
// Convert incoming JSON to validated DTO
$payload = $request->getBody();
$user = $mapper->toObject($payload, ValidatedUser::class);
use App\DTO\ValidatedUser;
// Your DTO with PHPDoc constraints
final readonly class ValidatedUser
{
public function __construct(
/** @var non-empty-string */
public string $name,
/** @var positive-int */
public int $age,
/** @var int<0, 100> */
public int $score,
) {}
}
// Generate schema (e.g., for OpenAPI documentation)
$schema = $mapper->toJsonSchema(ValidatedUser::class);
// Returns JSON schema with minLength, minimum constraints, etc.
// Validate and map incoming data
$jsonPayload = '{"name": "John Doe", "age": 25, "score": 85}';
$user = $mapper->toObject($jsonPayload, ValidatedUser::class);
// Returns ValidatedUser instance or throws validation exception
// Invalid data example
$invalidPayload = '{"name": "", "age": -5, "score": 150}';
$user = $mapper->toObject($invalidPayload, ValidatedUser::class);
// Throws validation exception: empty name, negative age, score out of range
#[Route('/users', methods: ['POST'])]
public function createUser(ServerRequestInterface $request): ResponseInterface
{
try {
// Map and validate incoming JSON to DTO
$user = $this->mapper->toObject(
$request->getBody()->getContents(),
ValidatedUser::class
);
// Process the validated user data
$this->userService->create($user);
return new JsonResponse(['success' => true]);
} catch (\CuyZ\Valinor\Mapper\MappingError $e) {
// Handle validation errors
return new JsonResponse([
'error' => 'Validation failed',
'details' => $e->getMessage()
], 400);
}
}