justinrainbow/json-schema
Validate JSON documents against JSON Schema in PHP. Supports Draft-3, Draft-4, Draft-6 and Draft-7 (coverage varies). Install via Composer and use JsonSchema\Validator to validate data with local file $ref schemas and inspect validation errors.
composer require justinrainbow/json-schema
use JsonSchema\Validator;
$validator = new Validator();
$data = json_decode(file_get_contents('data.json'));
$schema = json_decode(file_get_contents('schema.json'));
$validator->validate($data, $schema);
if ($validator->isValid()) {
// Data is valid
} else {
foreach ($validator->getErrors() as $error) {
logger()->error($error['message']);
}
}
Validate incoming API requests (e.g., Laravel Request object) against a schema:
use Illuminate\Http\Request;
use JsonSchema\Validator;
$validator = new Validator();
$requestData = (object) [
'name' => $request->input('name'),
'email' => $request->input('email')
];
$schema = json_decode('{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["name", "email"]
}');
$validator->validate($requestData, $schema);
Cache schemas in a SchemaStorage for performance and reusability:
use JsonSchema\SchemaStorage;
$storage = new SchemaStorage();
$storage->addSchema('user', json_decode(file_get_contents('schemas/user.json')));
$storage->addSchema('product', json_decode(file_get_contents('schemas/product.json')));
$validator = new Validator(new \JsonSchema\Constraints\Factory($storage));
Automatically convert string inputs to expected types (e.g., "true" → true):
$validator->coerce(
(object) ['active' => "true", 'price' => "19.99"],
(object) [
"type" => "object",
"properties" => [
"active" => ["type" => "boolean"],
"price" => ["type" => "number"]
]
]
);
Apply schema defaults dynamically:
$validator->validate(
(object) ['name' => 'John'],
(object) [
"type" => "object",
"properties" => [
"name" => ["type" => "string"],
"age" => ["type" => "integer", "default" => 18]
]
],
\JsonSchema\Constraints\Constraint::CHECK_MODE_APPLY_DEFAULTS
);
Use $ref for reusable components (e.g., nested objects):
$schema = json_decode('{
"definitions": {
"address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"}
}
}
},
"type": "object",
"properties": {
"user": {"$ref": "#/definitions/address"}
}
}');
$storage = new SchemaStorage();
$storage->addSchema('file://schema', $schema);
$validator = new Validator(new \JsonSchema\Constraints\Factory($storage));
Extend FormRequest to validate against JSON Schema:
use JsonSchema\Validator;
use Illuminate\Foundation\Http\FormRequest;
class StoreUserRequest extends FormRequest
{
protected function failedValidation(Validator $validator)
{
$jsonValidator = new Validator();
$jsonValidator->validate($this->validated(), $this->schema());
if (!$jsonValidator->isValid()) {
throw new \Exception("JSON Schema validation failed: " . implode(", ", $jsonValidator->getErrors()));
}
}
protected function schema()
{
return json_decode(file_get_contents('schemas/user.json'));
}
}
Centralize schema validation in Laravel middleware:
use Closure;
use JsonSchema\Validator;
class ValidateJsonSchema
{
public function handle($request, Closure $next)
{
$validator = new Validator();
$validator->validate($request->all(), $this->schema());
if (!$validator->isValid()) {
return response()->json([
'errors' => $validator->getErrors()
], 422);
}
return $next($request);
}
protected function schema()
{
return json_decode(file_get_contents('schemas/api.json'));
}
}
contentMediaType, contentEncoding) may require explicit configuration.const or contains behave differently. Test thoroughly when migrating schemas.CHECK_MODE_STRICT for Draft-6 to enforce full compliance (may break existing schemas).SchemaStorage instances:
$storage = app()->make(SchemaStorage::class); // Singleton in Laravel
$ref chains) can slow validation. Profile with microtime(true):
$start = microtime(true);
$validator->validate($data, $schema);
logger()->debug("Validation time: " . (microtime(true) - $start) . "s");
CHECK_MODE_COERCE_TYPES modifies the input object. Clone it first if immutability is required:
$validator->coerce(clone $requestData, $schema);
"true"/"false" are coerced to booleans, but "yes"/"no" are not. Use custom constraints for edge cases:
$validator->validate($data, $schema, Constraint::CHECK_MODE_COERCE_TYPES | Constraint::CHECK_MODE_TYPE_CAST);
file:// (not ./ or ../) for local schema references:
$storage->addSchema('file://' . realpath('schemas/user.json'), $schema);
{
"$ref": "#/definitions/self"
}
http:// or https:// for external schemas, but handle failures gracefully:
try {
$storage->addSchema('https://example.com/schema.json', $schema);
} catch (\JsonSchema\Exception\InvalidSchemaException $e) {
logger()->error("Schema fetch failed: " . $e->getMessage());
}
CHECK_MODE_EXCEPTIONS for stack traces:
$validator->validate($data, $schema, Constraint::CHECK_MODE_EXCEPTIONS);
$validator->validate($schema, $schema, Constraint::CHECK_MODE_VALIDATE_SCHEMA);
\JsonSchema\Constraints\AbstractConstraint for domain-specific rules (e.g., "email must be company domain").SchemaStorage and Validator in a service provider:
public function register()
{
$this->app->singleton(SchemaStorage::class, function () {
$storage = new SchemaStorage();
$storage->addSchema('user', json_decode(file_get_contents('schemas/user.json')));
return $storage;
});
$this->app->bind(Validator::class, function ($app) {
return new Validator(new \JsonSchema\Constraints\Factory($app->make(SchemaStorage::class)));
});
}
use JsonSchema\Validator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
class ValidateCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$validator = new Validator();
$data = json_decode($input->getArgument('json'), true);
$schema = json_decode(file_get_contents('schema.json'));
$validator->validate($data, $schema);
if (!$validator->isValid()) {
$output->writeln("<error>Validation failed:</error>");
foreach ($validator->getErrors() as $
How can I help you explore Laravel packages today?