justinrainbow/json-schema
Validate JSON data against JSON Schema in PHP. Supports draft-3, draft-4, draft-6, and draft-7 with $ref resolution and detailed validation errors. Install via Composer and validate decoded JSON objects against local or remote schemas.
Installation:
composer require justinrainbow/json-schema
Add to composer.json if using Laravel's autoloader:
"autoload": {
"psr-4": {
"App\\": "app/",
"JsonSchema\\": "vendor/justinrainbow/json-schema/src/"
}
}
Run composer dump-autoload.
First Use Case: Validate an incoming API request payload:
use JsonSchema\Validator;
$validator = new Validator();
$data = json_decode(request()->getContent(), true);
$schema = json_decode(file_get_contents('schemas/user.json'), false);
$validator->validate($data, $schema);
Quick Validation Check:
if ($validator->isValid()) {
return response()->json(['success' => true]);
}
return response()->json(['errors' => $validator->getErrors()], 400);
Workflow:
Define Schema:
Store schemas in resources/schemas/ (e.g., user.json):
{
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 2 },
"email": { "type": "string", "format": "email" }
},
"required": ["email"]
}
Validate Request:
public function store(Request $request) {
$validator = new Validator();
$schema = json_decode(file_get_contents('schemas/user.json'));
$data = $request->all();
$validator->validate($data, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
if (!$validator->isValid()) {
return response()->json(['errors' => $validator->getErrors()], 400);
}
// Proceed with logic...
}
Reusable Validator Service:
// app/Services/SchemaValidator.php
class SchemaValidator {
public function validate($data, string $schemaPath, int $flags = Constraint::CHECK_MODE_NORMAL) {
$validator = new Validator();
$schema = json_decode(file_get_contents($schemaPath));
$validator->validate($data, $schema, $flags);
return $validator;
}
}
Usage:
$validator = app(SchemaValidator::class)->validate($request->all(), 'schemas/user.json');
Extend Laravel's FormRequest:
use JsonSchema\Validator;
use Illuminate\Foundation\Http\FormRequest;
class StoreUserRequest extends FormRequest {
public function validateSchema() {
$validator = new Validator();
$schema = json_decode(file_get_contents('schemas/user.json'));
$validator->validate($this->all(), $schema, Constraint::CHECK_MODE_COERCE_TYPES);
if (!$validator->isValid()) {
throw new \Exception('Validation failed: ' . json_encode($validator->getErrors()));
}
}
}
Cache Schemas:
// app/Providers/AppServiceProvider.php
public function boot() {
SchemaStorage::getInstance()->addSchema(
'file://schemas/user',
json_decode(file_get_contents('schemas/user.json'))
);
}
Resolve References:
$validator = new Validator(new Factory(SchemaStorage::getInstance()));
$validator->validate($data, (object)['$ref' => 'file://schemas/user']);
Automate Data Transformation:
$validator->coerce($data, $schema); // Shorthand for coercion
$validator->validate($data, $schema, Constraint::CHECK_MODE_APPLY_DEFAULTS);
Example:
$data = ['active' => 'true', 'age' => '25'];
$schema = (object)[
'type' => 'object',
'properties' => (object)[
'active' => (object)['type' => 'boolean', 'default' => false],
'age' => (object)['type' => 'integer', 'default' => 18]
]
];
$validator->validate($data, $schema, Constraint::CHECK_MODE_COERCE_TYPES | Constraint::CHECK_MODE_APPLY_DEFAULTS);
Unit Tests:
use JsonSchema\Validator;
use PHPUnit\Framework\TestCase;
class UserSchemaTest extends TestCase {
public function testValidUser() {
$validator = new Validator();
$data = ['name' => 'John', 'email' => 'john@example.com'];
$schema = json_decode(file_get_contents('schemas/user.json'));
$validator->validate($data, $schema);
$this->assertTrue($validator->isValid());
}
}
Feature Tests:
public function testApiValidation() {
$response = $this->post('/api/users', ['name' => 'A', 'email' => 'invalid']);
$response->assertStatus(400);
$this->assertArrayHasKey('errors', $response->json());
}
Schema References:
$ref paths must be absolute (e.g., file://schemas/user#/definitions/role).SchemaStorage to resolve references:
$storage = new SchemaStorage();
$storage->addSchema('file://schemas/user', $schema);
$validator = new Validator(new Factory($storage));
Type Coercion Side Effects:
CHECK_MODE_COERCE_TYPES modifies the original $data.$validator->validate(clone $data, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
Draft Compatibility:
$validator->validate($data, $schema, Constraint::CHECK_MODE_STRICT);
Error Handling:
getErrors() returns nested arrays; flatten for API responses:
$errors = collect($validator->getErrors())->pluck('message')->toArray();
Performance:
$schema = Cache::remember('user_schema', 60, function() {
return json_decode(file_get_contents('schemas/user.json'));
});
Detailed Errors: Enable verbose error messages:
$validator->validate($data, $schema);
foreach ($validator->getErrors() as $error) {
dump($error['property'], $error['message'], $error['constraint']);
}
Schema Validation: Validate the schema itself:
$validator->validate($schema, $metaSchema, Constraint::CHECK_MODE_VALIDATE_SCHEMA);
Strict Mode:
Use CHECK_MODE_STRICT for Draft-6 to catch edge cases:
$validator->validate($data, $schema, Constraint::CHECK_MODE_STRICT);
Custom Constraints:
Extend JsonSchema\Constraints\AbstractConstraint to add custom validation logic.
Format Validators: Override format checks (e.g., custom date formats):
use JsonSchema\Constraints\Format\FormatChecker;
class CustomFormatChecker extends FormatChecker {
public function checkCustomFormat($value) {
// Custom logic
}
}
Schema Storage:
Extend SchemaStorage to load schemas from databases or APIs:
class DatabaseSchemaStorage extends SchemaStorage {
public function addSchema($id, $schema) {
// Fetch from DB instead of file
}
}
Service Provider: Bind the validator to the container:
$this->app->bind(Validator::class, function() {
return new Validator(new Factory(SchemaStorage::getInstance()));
});
Middleware: Validate all requests:
class ValidateSchemaMiddleware {
public function handle($request, Closure $next) {
$validator = app(Validator::class);
$validator->validate($request->all(), $this->getSchema($request));
if (!$validator->isValid()) {
abort(400, $validator->getErrors());
}
return $next($request);
}
}
Artisan Commands: Validate CLI input:
$validator = new Validator();
$data = json_decode($
How can I help you explore Laravel packages today?