Table of Contents

PHP RFC: JSON Schema validation support

Introduction

The JSON extension does not offer any way to structurally validate the content except for basic syntax checking. Being able to validate the actual structure would be very useful in many ways and has been requested many times.

The JSON Schema has become the primary method of validating JSON and has been widely adopted. As such, PHP would benefit from having that support in the JSON extension.

Proposal

The proposal is to add JSON Schema support to the JSON extension. Specifically, it adds support to json_decode and json_validate. This is done as an additional argument that accepts a schema object. The object can be created from a string. The error handling is consistent with the current handling.

API

The proposal introduces a new class JsonSchema that can be created only using a static method createFromString. It also introduces a new exception class JsonSchemaException that is a subclass of JsonException. The API stub is as follows:

<?php
 
class JsonSchemaException extends JsonException
{
}
 
/**
 * @not-serializable
 * @strict-properties
 */
final class JsonSchema
{
    private final function __construct() {}
 
    public static function createFromString(string $source): JsonSchema {}
}
 
// Addition of $schema paramater
function json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0, ?JsonSchema $schema = null): mixed {}
 
// Addition of $schema paramater
function json_validate(string $json, int $depth = 512, int $flags = 0, ?JsonSchema $schema = null): bool {}
 
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_ID
 */
const JSON_ERROR_SCHEMA_ID = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_KEYWORD_ALLOC
 */
const JSON_ERROR_SCHEMA_KEYWORD_ALLOC = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_KEYWORD_PREP
 */
const JSON_ERROR_SCHEMA_KEYWORD_PREP = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_KEYWORD_REQUIRED
 */
const JSON_ERROR_SCHEMA_KEYWORD_REQUIRED = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_KEYWORD_TYPE
 */
const JSON_ERROR_SCHEMA_KEYWORD_TYPE = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_REFERENCE_ALLOC
 */
const JSON_ERROR_SCHEMA_REFERENCE_ALLOC = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_REFERENCE_POINTER
 */
const JSON_ERROR_SCHEMA_REFERENCE_POINTER = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_REFERENCE_RESOLVE
 */
const JSON_ERROR_SCHEMA_REFERENCE_RESOLVE = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_REFERENCE_EXTERNAL
 */
const JSON_ERROR_SCHEMA_REFERENCE_EXTERNAL = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_REFERENCE_RECURSIVE
 */
const JSON_ERROR_SCHEMA_REFERENCE_RECURSIVE = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_ROOT_DATA_TYPE
 */
const JSON_ERROR_SCHEMA_ROOT_DATA_TYPE = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_STACK_ALLOC
 */
const JSON_ERROR_SCHEMA_STACK_ALLOC = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_TYPE_INVALID
 */
const JSON_ERROR_SCHEMA_TYPE_INVALID = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_URI_ALLOC
 */
const JSON_ERROR_SCHEMA_URI_ALLOC = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_URI_INVALID
 */
const JSON_ERROR_SCHEMA_URI_INVALID = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALIDATION_ALLOC
 */
const JSON_ERROR_SCHEMA_VALIDATION_ALLOC = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALIDATION_COMPOSITION
 */
const JSON_ERROR_SCHEMA_VALIDATION_COMPOSITION = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALIDATION_KEYWORD
 */
const JSON_ERROR_SCHEMA_VALIDATION_KEYWORD = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALIDATION_TYPE
 */
const JSON_ERROR_SCHEMA_VALIDATION_TYPE = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALIDATION_FALSE
 */
const JSON_ERROR_SCHEMA_VALIDATION_FALSE = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALUE_ALLOC
 */
const JSON_ERROR_SCHEMA_VALUE_ALLOC = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALUE_DATA_ALLOC
 */
const JSON_ERROR_SCHEMA_VALUE_DATA_ALLOC = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALUE_DATA_DEPS
 */
const JSON_ERROR_SCHEMA_VALUE_DATA_DEPS = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VALUE_DATA_TYPE
 */
const JSON_ERROR_SCHEMA_VALUE_DATA_TYPE = UNKNOWN;
/**
 * @var int
 * @cvalue JSO_SCHEMA_ERROR_VERSION
 */
const JSON_ERROR_SCHEMA_VERSION = UNKNOWN;
 
?>

The specified error codes are either used as exception codes or global error codes. This means it follows the current error handling in the JSON extension.

Also new messages are provided for the specific schema errors with exception message providing even more info about the actual error.

Backward Incompatible Changes

None

Proposed PHP Version(s)

PHP 8.5

Future Scope

The significant effort to develop the whole library for JSON Schema wasn't just to introduce validation. The main motivation was about what this might allow, primarily the ability to map JSON data to specific PHP classes and the ability to do early parsing termination, thus potentially eliminating hash DoS attacks. Specifically, the following features might be considered:

The above list contains more ideas that might be proposed, but each of them will, of course, require a separate RFC. There are more things that can be done as well. In addition to the schema-specific parts, the plan is to also look into the introduction of a new SIMD parser that would live in jso library and could be used by the JSON extension. That could result in significant performance gains.

Proposed Voting Choices

As per the voting RFC a yes/no vote with a 2/3 majority is needed for this proposal to be accepted.

Should JsonSchema validation be integrated to json extension?
Real name Yes No
Final result: 0 0
This poll has been closed.

The vote started on 2025-07-DD at mm:hh UTC and ends on 2015-07-DD at mm:hh UTC.

Patches and Tests

A working implementation has been added to jsond PECL extension and can be seen at https://github.com/bukka/php-jsond/tree/next. The jsond extension is almost the same as the json extension with some minor changes and different naming. This means that pretty much the same implementation can be added to the json extension.

The actual schema integration is done using my jso library that can be found at https://github.com/libjso/jso. This is an independent library initially developed for testing the json parser before it was migrated to jsond and later became the current implementation of the json extension. The development of JSON schema validation started a few years ago and has stabilized over time. There is still ongoing development to support more drafts. The current status is almost complete draft 4 and 6 support. Draft 7 support is coming soon. Other drafts might be available before the PHP RC1 version. The reason the development is separated is the possibility of introducing unit tests and specifically tailored integration tests. It is also much easier to debug and verify issues as well as experiment with new features.

The library is bundled and there is a sync script to synchronize the jso additions. The library allows specific virtual header overrides for virtual types. This means that it can effectively wrap internal PHP types and does not need to do extra copying to process the verified instances. The plan is to synchronize the release cycle of the library so it has stable branches that can be tracked by PHP branches.

Implementation

After the project is implemented, this section will contain

  1. the version(s) it was merged to
  2. a link to the git commit(s)