ropi/json-schema-evaluator

JSON Schema Evaluator

v0.4.3 2024-07-22 21:52 UTC

This package is auto-updated.

Last update: 2024-09-22 22:09:15 UTC


README

此库是基于PHP实现的用于评估和验证JSON Schemas的库。库可以轻松扩展以使用自己的关键字和草案。

要求

  • PHP >= 8.1
  • ext-bcmath
  • ext-mbstring
  • ext-fileinfo

目录

安装

可以通过使用composer从命令行界面安装此库。

支持的草案

草案 2020-12 (核心验证)

除了以下可选测试之外,通过了官方JSON schema测试套件的所有测试

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";
    }
}

上述示例的输出

Error keyword location: '/maxLength'
Error instance location: ''
Error message: At most 5 characters are allowed, but there are 10.

格式化结果

在以下示例中,结果被格式化为基本输出结构。此外,目前也支持标志输出结构

$formattedResults = (new \Ropi\JsonSchemaEvaluator\Output\BasicOutput($valid, $results))->format();
echo json_encode($formattedResults, JSON_PRETTY_PRINT);

上述示例的输出

{
  "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
    }
  ]
}

变异

默认值

如果使用default关键字定义了默认值,则它可以在评估期间自动应用。

$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"

内容解码

如果使用contentEncoding关键字定义了编码内容,则可以在评估期间自动解码。

$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"}'

高级示例

断言内容媒体类型

如果使用contentMediaType关键字定义了内容媒体类型,则可以在评估期间尊重它。

$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

断言格式

如果使用format关键字定义了格式,则可以在评估期间尊重它。

$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

短路

默认情况下,即使第一个关键字验证失败,也会评估所有关键字。如果激活短路,则在第一个负验证结果处停止评估。

$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

自定义关键字

可以将自定义关键字添加到草案中。
以下示例显示了如何实现一个实例必须匹配特定md5散列的关键字。

$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