This library is a PHP based implementation for evaluating and validating JSON Schemas. The library can be easily extended with your own keywords and drafts.
- PHP >= 8.1
- ext-bcmath
- ext-mbstring
- ext-fileinfo
The library can be installed from a command line interface by using composer.
Draft 2020-12 (Core and Validation)
Passes all tests of official JSON schema test suite except the following optional tests:
- optional/refOfUnknownKeyword.json: This means that you cannot use the $ref keyword to reference schemas that are located inside unknown keywords.
- optional/ecmascript-regex.json: This means that the specifics of Ecmascript regular expressions are not respected. Instead, regular expressions are evaluated as PERL regular expressions.
composer require ropi/json-schema-evaluator
$schema = json_decode('{
"type": "string",
"maxLength": 5
}');
$evaluator = new \Ropi\JsonSchemaEvaluator\JsonSchemaEvaluator();
// Each JSON Schema must be statically analyzed once.
$staticEvaluationContext = $evaluator->evaluateStatic($schema, new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: new \Ropi\JsonSchemaEvaluator\Draft\Draft202012()
));
$instance1 = "hello";
$evaluator->evaluate($instance1, $staticEvaluationContext); // Returns true
$instance2 = "helloworld";
$evaluator->evaluate($instance2, $staticEvaluationContext); // Returns false
$valid = $evaluator->evaluate(
instance: $instance2,
staticEvaluationContext: $staticEvaluationContext,
results: $results
);
foreach ($results as $result) {
/** @var $result \Ropi\JsonSchemaEvaluator\EvaluationContext\RuntimeEvaluationResult */
if ($result->type === 'error') {
echo "Error keyword location: '{$result->keywordLocation}'\n";
echo "Error instance location: '{$result->instanceLocation}'\n";
echo "Error message: {$result->error}\n";
}
}
Output of above example:
Error keyword location: '/maxLength'
Error instance location: ''
Error message: At most 5 characters are allowed, but there are 10.
In the following example, the results are formatted as Basic Output Structure. In addition, only the Flag Output Structure is also currently supported.
$formattedResults = (new \Ropi\JsonSchemaEvaluator\Output\BasicOutput($valid, $results))->format();
echo json_encode($formattedResults, JSON_PRETTY_PRINT);
Output of above example:
{
"valid": false,
"errors": [
{
"type": "annotation",
"valid": true,
"keywordLocation": "\/type",
"instanceLocation": "",
"keywordName": "type",
"error": "",
"errorMeta": null,
"annotation": [
"string"
]
},
{
"type": "error",
"valid": false,
"keywordLocation": "\/maxLength",
"instanceLocation": "",
"keywordName": "maxLength",
"error": "At most 5 characters are allowed, but there are 10.",
"errorMeta": null
}
]
}
If a default value is defined with the default keyword, it can be automatically applied during evaluation.
$schema = json_decode('{
"type": "object",
"required": ["lastname"],
"properties": {
"firstname": {
"default": "n/a"
}
}
}');
$evaluator = new \Ropi\JsonSchemaEvaluator\JsonSchemaEvaluator();
$staticEvaluationContext = $evaluator->evaluateStatic($schema, new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: new \Ropi\JsonSchemaEvaluator\Draft\Draft202012(
evaluateMutations: true
)
));
$instance = (object) [
'lastname' => 'Gauss'
];
$evaluator->evaluate($instance, $staticEvaluationContext);
echo $instance->firstname; // Prints "n/a"
If encoded content is defined with the contentEncoding keyword, it can be automatically decoded during evaluation.
$schema = json_decode('{
"contentMediaType": "application/json",
"contentEncoding": "base64"
}');
$evaluator = new \Ropi\JsonSchemaEvaluator\JsonSchemaEvaluator();
$staticEvaluationContext = $evaluator->evaluateStatic($schema, new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: new \Ropi\JsonSchemaEvaluator\Draft\Draft202012(
evaluateMutations: true
)
));
$instance = 'eyJmb28iOiAiYmFyIn0K'; // Base64 encoded JSON '{"foo": "bar"}'
$evaluator->evaluate($instance, $staticEvaluationContext); // Returns true
echo $instance; // Prints '{"foo": "bar"}'
If content media type is defined with the contentMediaType keyword, it can be respected during evaluation.
$schema = json_decode('{
"contentMediaType": "application/json"
}');
$evaluator = new \Ropi\JsonSchemaEvaluator\JsonSchemaEvaluator();
$staticEvaluationContext = $evaluator->evaluateStatic($schema, new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: new \Ropi\JsonSchemaEvaluator\Draft\Draft202012(
assertContentMediaTypeEncoding: true
)
));
$instance = '{"foo": "bar"}';
$evaluator->evaluate($instance, $staticEvaluationContext); // Returns true
$instance2 = 'invalidJSON';
$evaluator->evaluate($instance2, $staticEvaluationContext); // Returns false
If format is defined with the format keyword, it can be respected during evaluation.
$schema = json_decode('{
"format": "email"
}');
$evaluator = new \Ropi\JsonSchemaEvaluator\JsonSchemaEvaluator();
$staticEvaluationContext = $evaluator->evaluateStatic($schema, new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: new \Ropi\JsonSchemaEvaluator\Draft\Draft202012(
assertFormat: true
)
));
$instance = 'test@example.com';
$evaluator->evaluate($instance, $staticEvaluationContext, $runtimeEvaluationConfig); // Returns true
$instance2 = 'invalidEmail';
$evaluator->evaluate($instance2, $staticEvaluationContext, $runtimeEvaluationConfig); // Returns false
By default, all keywords are evaluated, even if the first keyword validation fails. If short circuiting is activated, the evaluation stops at the first negative validation result.
$config = new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: new \Ropi\JsonSchemaEvaluator\Draft\Draft202012(
shortCircuit: true
)
);
$schema = json_decode('{
"type": "integer"
}');
$evaluator = new \Ropi\JsonSchemaEvaluator\JsonSchemaEvaluator();
$staticEvaluationContext = $evaluator->evaluateStatic($schema, new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: new \Ropi\JsonSchemaEvaluator\Draft\Draft202012(
acceptNumericStrings: true
)
));
$instance = json_decode('6565650699413464649797946464646464649797979', false, 512, JSON_BIGINT_AS_STRING);
$evaluator->evaluate($instance, $staticEvaluationContext); // Returns true
It is possible to add custom keywords to a draft.
The following example shows how to implement a keyword where the instance must match a specific md5 hash.
$schema = json_decode('{
"md5Hash": "098f6bcd4621d373cade4e832627b4f6"
}');
class Md5HashKeyword extends \Ropi\JsonSchemaEvaluator\Keyword\AbstractKeyword implements \Ropi\JsonSchemaEvaluator\Keyword\RuntimeKeywordInterface
{
public function getName() : string
{
return "md5Hash";
}
public function evaluate(mixed $keywordValue, \Ropi\JsonSchemaEvaluator\EvaluationContext\RuntimeEvaluationContext $context): ?\Ropi\JsonSchemaEvaluator\EvaluationContext\RuntimeEvaluationResult
{
$instance = $context->getCurrentInstance();
if (!is_string($instance)) {
// Ignore keyword, because instance is not a string
return null;
}
$result = $context->createResultForKeyword($this, $keywordValue);
if (md5($instance) !== $keywordValue) {
$result->invalidate('MD5 hash of "' . $instance . '" does not match ' . $keywordValue);
}
return $result;
}
}
$draft = new \Ropi\JsonSchemaEvaluator\Draft\Draft202012();
$draft->registerKeyword(new Md5HashKeyword(), 'https://example.tld/draft/2022-03/vocab/md5'); // Register keyword with custom vocabulary
$evaluator = new \Ropi\JsonSchemaEvaluator\JsonSchemaEvaluator();
$staticEvaluationContext = $evaluator->evaluateStatic($schema, new \Ropi\JsonSchemaEvaluator\EvaluationConfig\StaticEvaluationConfig(
defaultDraft: $draft
));
$instance = 'test';
$evaluator->evaluate($instance, $staticEvaluationContext); // Returns true, because md5 hash matches
$instance = 'hello';
$evaluator->evaluate($instance, $staticEvaluationContext); // Returns false, because md5 hash does not match