Swaggest JSON-schema implementation for PHP

Overview

Swaggest JSON-schema implementation for PHP

Build Status codecov time tracker Code lines Comments

High definition PHP structures with JSON-schema based validation.

Supported schemas:

Installation

composer require swaggest/json-schema

Usage

Structure definition can be done either with json-schema or with PHP class extending Swaggest\JsonSchema\Structure\ClassStructure

Validating JSON data against given schema

Define your json-schema

$schemaJson = <<<'JSON'
{
    "type": "object",
    "properties": {
        "id": {
            "type": "integer"
        },
        "name": {
            "type": "string"
        },
        "orders": {
            "type": "array",
            "items": {
                "$ref": "#/definitions/order"
            }
        }
    },
    "required":["id"],
    "definitions": {
        "order": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "integer"
                },
                "price": {
                    "type": "number"
                },
                "updated": {
                    "type": "string",
                    "format": "date-time"
                }
            },
            "required":["id"]
        }
    }
}
JSON;

Load it

use Swaggest\JsonSchema\Schema;
$schema = Schema::import(json_decode($schemaJson));

Validate data

properties:orders->items[1]->#/definitions/order">
$schema->in(json_decode(<<<'JSON'
{
    "id": 1,
    "name":"John Doe",
    "orders":[
        {
            "id":1
        },
        {
            "price":1.0
        }
    ]
}
JSON
)); // Exception: Required property missing: id at #->properties:orders->items[1]->#/definitions/order

You can also call Schema::import on string uri to schema json data.

$schema = Schema::import('http://localhost:1234/my_schema.json');

Or with boolean argument.

$schema = Schema::import(true); // permissive schema, always validates
$schema = Schema::import(false); // restrictive schema, always invalidates

Understanding error cause

With complex schemas it may be hard to find out what's wrong with your data. Exception message can look like:

properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[0] 1: Enum failed, enum: ["b"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[1] 2: No valid results for anyOf { 0: Enum failed, enum: ["c"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[0] 1: Enum failed, enum: ["d"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[1] 2: Enum failed, enum: ["e"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[2] } at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde] } at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo">
No valid results for oneOf {
 0: Enum failed, enum: ["a"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[0]
 1: Enum failed, enum: ["b"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[1]
 2: No valid results for anyOf {
   0: Enum failed, enum: ["c"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[0]
   1: Enum failed, enum: ["d"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[1]
   2: Enum failed, enum: ["e"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[2]
 } at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]
} at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo

For ambiguous schemas defined with oneOf/anyOf message is indented multi-line string.

Processing path is a combination of schema and data pointers. You can use InvalidValue->getSchemaPointer() and InvalidValue->getDataPointer() to extract schema/data pointer.

You can receive Schema instance that failed validation with InvalidValue->getFailedSubSchema.

You can build error tree using InvalidValue->inspect().

PHP structured classes with validation

price ); // Define additional mapping $ownerSchema->addPropertyMapping('DaTe_TiMe', Order::names()->dateTime, self::FANCY_MAPPING); $ownerSchema->addPropertyMapping('Id', Order::names()->id, self::FANCY_MAPPING); $ownerSchema->addPropertyMapping('PrIcE', Order::names()->price, self::FANCY_MAPPING); } }">
/**
 * @property int $quantity PHPDoc defined dynamic properties will be validated on every set
 */
class User extends ClassStructure
{
    /* Native (public) properties will be validated only on import and export of structure data */

    /** @var int */
    public $id;
    public $name;
    /** @var Order[] */
    public $orders;

    /** @var UserInfo */
    public $info;

    /**
     * @param Properties|static $properties
     * @param Schema $ownerSchema
     */
    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        // You can add custom meta to your schema
        $dbTable = new DbTable;
        $dbTable->tableName = 'users';
        $ownerSchema->addMeta($dbTable);

        // Setup property schemas
        $properties->id = Schema::integer();
        $properties->id->addMeta(new DbId($dbTable)); // You can add meta to property.

        $properties->name = Schema::string();

        // You can embed structures to main level with nested schemas
        $properties->info = UserInfo::schema()->nested();

        // You can set default value for property
        $defaultOptions = new UserOptions();
        $defaultOptions->autoLogin = true;
        $defaultOptions->groupName = 'guest';
        // UserOptions::schema() is safe to change as it is protected with lazy cloning
        $properties->options = UserOptions::schema()->setDefault(UserOptions::export($defaultOptions));

        // Dynamic (phpdoc-defined) properties can be used as well
        $properties->quantity = Schema::integer();
        $properties->quantity->minimum = 0;

        // Property can be any complex structure
        $properties->orders = Schema::create();
        $properties->orders->items = Order::schema();

        $ownerSchema->required = array(self::names()->id);
    }
}

class UserInfo extends ClassStructure {
    public $firstName;
    public $lastName;
    public $birthDay;

    /**
     * @param Properties|static $properties
     * @param Schema $ownerSchema
     */
    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        $properties->firstName = Schema::string();
        $properties->lastName = Schema::string();
        $properties->birthDay = Schema::string();
    }
}

class UserOptions extends ClassStructure
{
    public $autoLogin;
    public $groupName;

    /**
     * @param Properties|static $properties
     * @param Schema $ownerSchema
     */
    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        $properties->autoLogin = Schema::boolean();
        $properties->groupName = Schema::string();
    }
}

class Order implements ClassStructureContract
{
    use ClassStructureTrait; // You can use trait if you can't/don't want to extend ClassStructure

    const FANCY_MAPPING = 'fAnCy'; // You can create additional mapping namespace

    public $id;
    public $userId;
    public $dateTime;
    public $price;

    /**
     * @param Properties|static $properties
     * @param Schema $ownerSchema
     */
    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        // Add some meta data to your schema
        $dbMeta = new DbTable();
        $dbMeta->tableName = 'orders';
        $ownerSchema->addMeta($dbMeta);

        // Define properties
        $properties->id = Schema::integer();
        $properties->userId = User::properties()->id; // referencing property of another schema keeps meta
        $properties->dateTime = Schema::string();
        $properties->dateTime->format = Format::DATE_TIME;
        $properties->price = Schema::number();

        $ownerSchema->setFromRef('#/definitions/order');

        // Define default mapping if any.
        $ownerSchema->addPropertyMapping('date_time', Order::names()->dateTime);

        // Use mapped name references after the default mapping was configured.
        $names = self::names($ownerSchema->properties);
        $ownerSchema->required = array(
            $names->id,         
            $names->dateTime, // "date_time"
            $names->price       
        );

        // Define additional mapping
        $ownerSchema->addPropertyMapping('DaTe_TiMe', Order::names()->dateTime, self::FANCY_MAPPING);
        $ownerSchema->addPropertyMapping('Id', Order::names()->id, self::FANCY_MAPPING);
        $ownerSchema->addPropertyMapping('PrIcE', Order::names()->price, self::FANCY_MAPPING);
    }
}

Validation of dynamic properties is performed on set, this can help to find source of invalid data at cost of some performance drop

$user = new User();
$user->quantity = -1; // Exception: Value more than 0 expected, -1 received

Validation of native properties is performed only on import/export

$user = new User();
$user->quantity = 10;
User::export($user); // Exception: Required property missing: id

Error messages provide a path to invalid data

$user = new User();
$user->id = 1;
$user->name = 'John Doe';

$order = new Order();
$order->dateTime = (new \DateTime())->format(DATE_RFC3339);
$user->orders[] = $order;

User::export($user); // Exception: Required property missing: id at #->properties:orders->items[0]

Nested structures

Nested structures allow you to make composition: flatten several objects in one and separate back.

assertSame($json, json_encode($exported, JSON_PRETTY_PRINT)); $imported = User::import(json_decode($json)); $this->assertSame('John', $imported->info->firstName); $this->assertSame('Doe', $imported->info->lastName);">
$user = new User();
$user->id = 1;

$info = new UserInfo();
$info->firstName = 'John';
$info->lastName = 'Doe';
$info->birthDay = '1970-01-01';
$user->info = $info;

$json = <<
   
{
    "id": 1,
    "firstName": "John",
    "lastName": "Doe",
    "birthDay": "1970-01-01"
}
JSON;
$exported = User::export($user);
$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));

$imported = User::import(json_decode($json));
$this->assertSame('John', $imported->info->firstName);
$this->assertSame('Doe', $imported->info->lastName);

You can also use \Swaggest\JsonSchema\Structure\Composition to dynamically create schema compositions. This can be helpful to deal with results of database query on joined data.

import(json_decode($json)); // Get particular object with `pick` accessor $info = UserInfo::pick($object); $order = Order::pick($object); // Data is imported objects of according classes $this->assertTrue($order instanceof Order); $this->assertTrue($info instanceof UserInfo); $this->assertSame(1, $order->id); $this->assertSame('John', $info->firstName); $this->assertSame('Doe', $info->lastName); $this->assertSame(2.66, $order->price);">
$schema = new Composition(UserInfo::schema(), Order::schema());
$json = <<
   
{
    "id": 1,
    "firstName": "John",
    "lastName": "Doe",
    "price": 2.66
}
JSON;
$object = $schema->import(json_decode($json));

// Get particular object with `pick` accessor
$info = UserInfo::pick($object);
$order = Order::pick($object);

// Data is imported objects of according classes
$this->assertTrue($order instanceof Order);
$this->assertTrue($info instanceof UserInfo);

$this->assertSame(1, $order->id);
$this->assertSame('John', $info->firstName);
$this->assertSame('Doe', $info->lastName);
$this->assertSame(2.66, $order->price);

Keys mapping

If property names of PHP objects should be different from raw data you can call ->addPropertyMapping on owner schema.

// Define default mapping if any
$ownerSchema->addPropertyMapping('date_time', Order::names()->dateTime);

// Define additional mapping
$ownerSchema->addPropertyMapping('DaTe_TiMe', Order::names()->dateTime, self::FANCY_MAPPING);
$ownerSchema->addPropertyMapping('Id', Order::names()->id, self::FANCY_MAPPING);
$ownerSchema->addPropertyMapping('PrIcE', Order::names()->price, self::FANCY_MAPPING);

It will affect data mapping:

assertSame($json, json_encode($exported, JSON_PRETTY_PRINT)); $imported = Order::import(json_decode($json)); $this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);">
$order = new Order();
$order->id = 1;
$order->dateTime = '2015-10-28T07:28:00Z';
$order->price = 2.2;
$exported = Order::export($order);
$json = <<
   
{
    "id": 1,
    "date_time": "2015-10-28T07:28:00Z",
    "price": 2.2
}
JSON;
$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));

$imported = Order::import(json_decode($json));
$this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);

You can have multiple mapping namespaces, controlling with mapping property of Context

assertSame($json, json_encode($exported, JSON_PRETTY_PRINT)); $imported = Order::import(json_decode($json), $options); $this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);">
$options = new Context();
$options->mapping = Order::FANCY_MAPPING;

$exported = Order::export($order, $options);
$json = <<
   
{
    "Id": 1,
    "DaTe_TiMe": "2015-10-28T07:28:00Z",
    "PrIcE": 2.2
}
JSON;
$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));

$imported = Order::import(json_decode($json), $options);
$this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);

You can create your own pre-processor implementing Swaggest\JsonSchema\DataPreProcessor.

Meta

Meta is a way to complement Schema with your own data. You can keep and retrieve it.

You can store it.

$dbMeta = new DbTable();
$dbMeta->tableName = 'orders';
$ownerSchema->addMeta($dbMeta);

And get back.

// Retrieving meta
$dbTable = DbTable::get(Order::schema());
$this->assertSame('orders', $dbTable->tableName);

Mapping without validation

If you want to tolerate invalid data or improve mapping performance you can specify skipValidation flag in processing Context

assertSame(4, $res->one);">
$schema = Schema::object();
$schema->setProperty('one', Schema::integer());
$schema->properties->one->minimum = 5;

$options = new Context();
$options->skipValidation = true;

$res = $schema->in(json_decode('{"one":4}'), $options);
$this->assertSame(4, $res->one);

Overriding mapping classes

If you want to map data to a different class you can register mapping at top level of your importer structure.

class CustomSwaggerSchema extends SwaggerSchema
{
    public static function import($data, Context $options = null)
    {
        if ($options === null) {
            $options = new Context();
        }
        $options->objectItemClassMapping[Schema::className()] = CustomSchema::className();
        return parent::import($data, $options);
    }
}

Or specify it in processing context

$context = new Context();
$context->objectItemClassMapping[Schema::className()] = CustomSchema::className();
$schema = SwaggerSchema::schema()->in(json_decode(
    file_get_contents(__DIR__ . '/../../../../spec/petstore-swagger.json')
), $context);
$this->assertInstanceOf(CustomSchema::className(), $schema->definitions['User']);

Code quality and test coverage

Some code quality best practices are deliberately violated here ( see Scrutinizer Code Quality ) to allow best performance at maintenance cost.

Those violations are secured by comprehensive test coverage:

Contributing

Issues and pull requests are welcome!

Development supported by JetBrains.

Comments
  • How can i filter data with php-json-schema

    How can i filter data with php-json-schema

    for examle json data:

    {
    "name":"jedi", 
    "addr":"xxxxxxxx", 
    "phone":"1122330"
    }
    

    json-schema data:

    {
        "type": "object",
        "properties": {
            "name": {
                "type":"string",
            },
            "addr": {
                "type":"string"
            }
        },
        "required": ["name","addr"]
    }
    

    after validate the data , i want to get the data

    {
    "name":"jedi", 
    "addr":"xxxxxxxx"
    }
    
    enhancement 
    opened by jediliang 16
  • Handle Enum as array

    Handle Enum as array

    I use your library since, well... a few hours and I have to say it's great. I just have a problem with my schema validation, I described it with a property which can be an array or a integer like this

    "inputStates": { "type": ["string", "array"] }

    The schema import is doing well but when I'm trying to parse my JSON data, I get this exception

    PHP Swaggest\\JsonSchema\\Exception\\EnumException: Enum failed, enum: ["array","boolean","integer","null","number","object","string"], data: ["string","array"] at #->properties:definitions->additionalProperties:command->properties:properties->additionalProperties:inputStates->properties:type->anyOf[0]

    I don't know if this is a real issue or just me misunderstanding the library but it should be great if the processEnum function handles the array.

    Tell me what you think about it :)

    opened by kranack 15
  • Cannot use local JSON file

    Cannot use local JSON file

    I'm using php-json-schema to validate the responses of my server endpoints.

    I'm testing it locally and i can't seem to find a way to reference another schma with definitions.

    My endpoint schema validator has the following: "tasks": { "$ref": "definitions.json#/taskDefinition"},

    The other file called definitions.json has the following:

    {
      "taskDefinition": {
        "type": "array"
      }
    }
    

    It always returns an error:

    file_get_contents(definitions.json): failed to open stream: No such file or directory
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/RemoteRef/BasicFetcher.php:14
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/RefResolver.php:195
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:698
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:1139
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Wrapper.php:46
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:863
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:1139
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:831
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:1139
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:176
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Wrapper.php:58
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Structure/ClassStructureTrait.php:60
     /home/ralves/projects/php-project/vendor/swaggest/json-schema/src/Schema.php:137
     /home/ralves/projects/php-project/tests/api/AuthenticatedCest.php:77
     /home/ralves/projects/php-project/tests/api/CoreCest.php:12
     /tmp/ide-codeception.php:494
    

    I tried using id: "file://", and a bunch of other things but didn't found a way to make it work.

    I changed the code of the BasicFetcher.php so that the $url is the absolute path of the definitions.jsonfile and it worked, but clearly it's not a solution for this problem.

    PS: Searching on google for similar problem got me on this issue of another project

    opened by iursevla 8
  • Composer detected issues in your platform: Your Composer dependencies require a PHP version = 7.1.0"">

    Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 7.1.0"

    Just composer require "swaggest/json-schema" for new project and got %subj%.

    Looked at what changed https://github.com/swaggest/php-json-schema/compare/v0.12.38...v0.12.39 and found out that added "Polyfill dependency for mbstring extension".

    Do these directives in the package.json reflect your true intent?

      "require": {
        "symfony/polyfill-mbstring": "^1.19"
      },
      "suggest": {
        "ext-mbstring": "For better performance"
      }
    

    I'm asking because the requirements have changed significantly with introduction of symfony/polyfill-mbstring package.

            {
                "name": "symfony/polyfill-mbstring",
                "version": "v1.23.1",
    
                "require": {
                    "php": ">=7.1"
                },
    

    Thanks!

    opened by massadm 6
  • Validation will always pass with invalid JSON schema

    Validation will always pass with invalid JSON schema

    Hello, I encountered a problem when schema with invalid JSON is imported from URL or path - all validation then will pass without any errors or warnings.

    In this pull request, I added a very simple test case that demonstrates no exception is thrown when importing or validating.

    Where is the problem?

    BasicFetcher returns null in case json_decode fail.

    Then in RefResolver there is check only against the false return value, with null returned whole process silently continues and all validation will pass with any input value.

    The quick fix is very easy, just check the return value for null in RefResolver and throw an exception. But I'd like to ask first if this fix is enough for you.

    Something like

    if ($rootData === null) {
        throw new Exception("Failed to decode content from $url", Exception::RESOLVE_FAILED);
    }
    

    Thanks for your time :slightly_smiling_face:

    opened by lku 6
  • Support typed properties

    Support typed properties

    PHP doesn't allow typed properties to be accessed if it is uninitialized:

    PHP Fatal error:  Uncaught Error: Typed property Order::$user_id must not be accessed before initialization
    

    This change skips properties if it is not set. I know this library doesn't really support PHP type declarations, but this is a pretty harmless change that does not affect existing functionality (:

    opened by bruceoutdoors 5
  • Definition not found in a schema with an URN as $id

    Definition not found in a schema with an URN as $id

    {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "$id": "urn:example:api:response-example",
        "type": "object",
        "additionalProperties": false,
        "required": ["confirmed", "to_pay"],
        "definitions": {
            "example_item": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_type",
                    "count"
                ],
                "properties": {
                    "_type": {
                        "$id": "#/definitions/example_item/properties/confirmed/properties/_type",
                        "type": "string",
                        "enum": ["example_item"]
                    },
                    "count": {
                        "$id": "#/definitions/example_item/properties/confirmed/properties/count",
                        "type": "integer"
                    }
                }
            }
        },
        "properties": {
            "confirmed": {
                "$ref": "#/definitions/example_item"
            },
            "to_pay": {
                "$ref": "#/definitions/example_item"
            }
        }
    }
    

    The error

    file_get_contents(/var/www/ax/ef/../../schemas/xyz/abc/def/../../urn:example:api:response-example): failed to open stream: No such file or directory
    
    opened by williamdes 5
  • Export properties with null as a value

    Export properties with null as a value

    Hi,

    I noticed that properties having null as a value are not exported. These properties are just missing in an exported JSON file. But I wanted/needed all these properties in my current project - so I created a "feature" for it.

    You can trigger this behavior with an option, which is passed through to the jsonSerialize method. It's not the nicest way to control it, but I didn't find another way. Feedback very welcome.

    Output without option: { "id": 123, "foo": "bar" }

    Output with option: { "id": 123, "foo": "bar", "null_property": null }

    opened by fabfischer 4
  • GOTO operator in PHP, really?

    GOTO operator in PHP, really?

    https://github.com/swaggest/php-json-schema/blob/0dbf9e7857d065bde50605972933d6c38dbe6b8c/src/Schema.php#L1186

    I just spotted this in your code base on the very first look, while i was evaluating if i should use your lib...

    opened by christian-weiss 3
  • Permit installation when symfony/polyfill-mbstring is present

    Permit installation when symfony/polyfill-mbstring is present

    I propose to require the symfony/polyfill-mbstring package, instead of the mbstring extension, to permit usage of this library on PHP server that does not have the extension loaded. For instance, it can happen on some shared hosting servers where users cannot choose which extension is installed.

    hacktoberfest 
    opened by cedric-anne 3
  • Schema->processObjectRequired() does not handle property mappings

    Schema->processObjectRequired() does not handle property mappings

    If I have a property name mediaType and a property mapping defining its data name as media_type, I get an error that mediaType is missing but it should be looking for media_type.

    Example from my entity's setUpProperties():

    $ownerSchema->required = [static::names()->mediaType];
    $ownerSchema->addPropertyMapping('media_type', static::names()->mediaType);
    

    Am I missing something or should this work the way I'm expecting? Seems like the property mapping needs to be applied here: https://github.com/swaggest/php-json-schema/blob/d0126bd830c92a498691d278f01d3ce13a2c9c0a/src/Schema.php#L556

    opened by mmelvin0 3
  • processingPath/schemaPointer

    processingPath/schemaPointer

    Hello @vearutop and all contributors! I have a question.

    https://github.com/swaggest/json-diff/releases/tag/v3.10.4 https://github.com/swaggest/php-json-schema/releases/tag/v0.12.41 PHP 5.6.33

    JSON Schema:

    {
        "$schema": "https://json-schema.org/draft-04/schema#",
        "type": "object",
        "properties": {
            "items": {
                "type": "array",
                "items": {
                    "allOf": [
                        { "type": "number" },
                        { "type": "integer", "minimum": 1 },
                        { "type": "integer", "maximum": 2 }
                    ]
                }
            }
        }
    }
    

    Data:

    {
        "items": [
            1,
            "z"
        ]
    }
    

    Expected error:

    Swaggest\JsonSchema\Exception\Error Object
    (
        [error] => Number expected, "z" received
        [schemaPointers] => Array
            (
                [0] => /properties/items/items/1/allOf/0
            )
    
        [dataPointer] => /items/1
        [processingPath] => #->properties:items->items[1]:1->allOf[0]
        [subErrors] => 
    )
    

    But what does :1 mean in processingPath and why exception 'Swaggest\JsonDiff\JsonPointerException' with message 'Key not found: 1' in ./vendor/swaggest/json-diff/src/JsonPointer.php:226 happening on trying JsonPointer::getByPointer() with JSON Schema above and Error->getSchemaPointer() as a pointer?

    opened by massadm 1
  • References Are Resolved Despite Setting Dereference to False

    References Are Resolved Despite Setting Dereference to False

    For my use case I'm encountering challenges importing schemas that contain a lot of references, so I wanted to try an approach where I would import the schema initially without resolving references and get those filled in later. Unfortunately, I found that even if I set the dereference option to false in the context, references are still attempting to be resolved. Below is a test example I put together to demonstrate:

    <?php
    
    /**
     * Integration test for expected behavior from schemas.
     *
     * @coversNothing
     */
    class SchemaIntegrationTest extends UnitTestCase {
    
      use ProphecyTrait;
    
      protected string $schemaWithReferenceJson = <<<JSON
        {
          "\$schema": "http://json-schema.org/draft-04/schema#",
          "category": "test",
          "title": "Schema with reference",
          "type": "object",
          "format": "grid",
          "properties": {
            "reference_property": {
              "\$ref": "my/example/reference"
            }
          }
        }
        JSON;
    
    
      /**
       * Test schema dereferencing behavior.
       */
      public function testSchemaDereferencing() {
        /** @var \Swaggest\JsonSchema\RemoteRefProvider $refProvider */
        $refProvider = $this->prophesize(RemoteRefProvider::class);
        $refProvider->getSchemaData('my/example/reference')
          ->willReturn((object) [])
          ->shouldNotBeCalled();
    
        $context = new Context();
        $context->setRemoteRefProvider($refProvider->reveal());
        $context->dereference = FALSE;
        
        $schema_data = json_decode($this->schemaWithReferenceJson);
    
        $schema = Schema::import($schema_data, $context);
    
        $schema_output = json_encode($schema);
        $this->assertStringContainsString('my/example/reference', $schema_output);
      }
    
    }
    

    This test fails with the following output:

    Failed asserting that '{"$schema":"http://json-schema.org/draft-04/schema#","title":"Schema with reference","properties":{"reference_property":{}},"type":"object","format":"grid","category":"test"}' contains "my/example/reference".

    If I comment out the final assertion that causes the test to fail immediately, I get the following output from the Prophecy prediction checks confirming that the ref provider was called:

    Some predictions failed:
    Double\RemoteRefProvider\P1:
      No calls expected that match:
          Double\RemoteRefProvider\P1->getSchemaData(exact("my/example/reference"))
        but 1 was made:
          - getSchemaData("my/example/reference") @ vendor/swaggest/json-schema/src/RefResolver.php:196
     /var/www/html/vendor/phpspec/prophecy-phpunit/src/ProphecyTrait.php:61
     /var/www/html/vendor/phpunit/phpunit/src/Framework/TestResult.php:726
     /var/www/html/vendor/phpunit/phpunit/src/Framework/TestSuite.php:670
     /var/www/html/vendor/phpunit/phpunit/src/Framework/TestSuite.php:670
     /var/www/html/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:673
     /var/www/html/vendor/phpunit/phpunit/src/TextUI/Command.php:143
     /var/www/html/vendor/phpunit/phpunit/src/TextUI/Command.php:96
    
    opened by slucero 3
  • Inconsistent Validation Result Output Makes it Difficult to Identify Applied Composition Rules

    Inconsistent Validation Result Output Makes it Difficult to Identify Applied Composition Rules

    Background

    I've been using this library to work with JSON schemas and do some traversal of the schema alongside configuration data to match it. As part of this need, I need to be able to identify both the configuration value being processed at a certain level, and the related schema property the value maps to. In most cases, this works pretty well as I can traverse the properties of the schema and the configuration data in parallel, but it gets more difficult whenever I encounter anything in the schema with composition rules like anyOf or oneOf.

    Whenever I'm traversing the schema and encounter one of these rules, the best solution I've come to was passing the data at that level into the schema for validation and checking the document path on the result to determine which rule was used. This works for complex data when an ObjectItem is returned and the document path is available, but when simpler values, like a string, are passed in, the value itself is returned and a document path is unavailable.

    Examples

       $schema_json = <<<JSON
        {
          "\$schema": "http://json-schema.org/draft-04/schema#",
          "title": "Example",
          "type": "object",
          "properties": {
            "text": {
              "title": "Text",
              "type": "string",
              "options": {
                "grid_columns": 4
              }
            },
            "nested_items": {
              "title": "Nested items of various types",
              "type": "array",
              "items": {
                "anyOf": [
                  {
                    "title": "Simple object",
                    "type": "object"
                  },
                  {
                    "title": "Simple string",
                    "type": "string"
                  }
                ]
              }
            }
          }
        }
        JSON;
    
        $string_value = [
          'my_string',
        ];
    
        $object_value = [
          (object) [
            'my_object' => 'my_value',
          ],
        ];
    
        // Import the whole schema to start with.
        $schema = Schema::import(json_decode($schema_json));
        // Traverse to a lower property containing composition rules to be tested.
        $nested = $schema->getProperties()->nested_items;
    
        // Test a more complex value. This will return an ObjectItem.
        $object_result = $nested->in($object_value);
        echo $object_result[0]->getDocumentPath(); // "#->items[0]:0->anyOf[0]"
    
        // Test a simple value. This will return the value itself.
        $string_result = $nested->in($string_value);
        echo $string_result[0] // "my_string"
    

    Based on this example, is there something I'm doing wrong or a better way to determine which composition rule(s) apply to a value? Currently I'm testing if the result is an instance of ObjectItemContract, and if it is I'm doing my own cycling through the composition rules to test, but ideally this step wouldn't be required if the return value for a validation result were consistent.

    opened by slucero 1
  • Maximum/Minimum error messages seem to be reversed

    Maximum/Minimum error messages seem to be reversed

    In Swaggest\JsonSchema\Schema::processNumeric the error messages regarding maximum/minimum and exclusive maximum/minimum seem to be reversed. At the maximum it says "Value less than ..." and at the exclusive maximum it says "Value less or equal than...". I think it needs to be the other way round. The same for minimum.

    opened by glamic-ep 1
  • Frustrated to understand the error structure

    Frustrated to understand the error structure

    Here is an error I got when I try to validate

    JSON Data

    {
        "name": "Slideshow",
        "tag": "section",
        "class": "slideshow",
        "max_blocks": 5,
        "settings": [
            {
                "id": "title",
                "type": "text",
                "label": "Slideshow"
            }
        ],
        "blocks": [
            {
                "name": "Slide",
                "type": "slide",
                "settings": [
                    {
                        "type": "image_picker",
                        "id": "image",
                        "label": "Image"
                    }
                ]
            }
        ]
    }
    
    

    JSON schema

    {
        "$id": "/schemas/section",
        "type": "object",
        "properties": {
            "name": { "type": "string"},
            "tag": { "type": "string"},
            "class": { "type": "string"},
            "blocks": {
                "$ref": "file://block.json"
            },
            "settings": {
                "$ref": "file://settings.json"
            },
            "max_blocks": {
                "type": "integer"
            },
            "presets": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": {"type": "string"},
                        "blocks": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "type": {"type": "string"}
                                }
                            }
                        }
                    }
                }
            },
            "default": {
                "type": "object",
                "properties": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "type": {"type": "string"}
                        }
                    }
                }
            }
        },
        "required": ["name"]
    }
    
    

    The error I got.

    No valid results for anyOf {
      0: No valid results for anyOf {
       0: Array expected, "id" received at #->properties:properties->additionalProperties:blocks->properties:items->anyOf[0]->$ref:file://block.json->properties:properties->additionalProperties:settings->$ref:file://settings.json->properties:items->anyOf[0]->properties:oneOf->items[5]:5->$ref:file://types/image_picker.json->properties:required at #->properties:properties->additionalProperties:blocks->properties:items->anyOf[0]->$ref:file://block.json->properties:properties->additionalProperties:settings->$ref:file://settings.json->properties:items->anyOf[0]->properties:oneOf->items[5]:5
       1: Array expected, {"oneOf":[{"$ref":"file:\/\/types\/checkbox.json"},{"$ref":"file:\/\/types\/collection.json"},{"$ref":"file:\/\/types\/color_picker.json"},{"$ref":"file:\/\/types\/color.json"},{"$ref":"file:\/\/types\/header.json"},{"$ref":"file:\/\/types\/image_picker.json"},{"$ref":"file:\/\/types\/link_list.json"},{"$ref":"file:\/\/types\/product.json"},{"$ref":"file:\/\/types\/radio.json"},{"$ref":"file:\/\/types\/range.json"},{"$ref":"file:\/\/types\/richtext.json"},{"$ref":"file:\/\/types\/select.json"},{"$ref":"file:\/\/types\/text.json"},{"$ref":"file:\/\/types\/textarea.json"},{"$ref":"file:\/\/types\/url.json"},{"$ref":"file:\/\/types\/video_url.json"},{"$ref":"file:\/\/types\/video.json"}]} received at #->properties:properties->additionalProperties:blocks->properties:items->anyOf[0]->$ref:file://block.json->properties:properties->additionalProperties:settings->$ref:file://settings.json->properties:items->anyOf[1]
     } at #->properties:properties->additionalProperties:blocks->properties:items->anyOf[0]->$ref:file://block.json->properties:properties->additionalProperties:settings->$ref:file://settings.json->properties:items at #->properties:properties->additionalProperties:blocks->properties:items->anyOf[0]->$ref:file://block.json->properties:properties->additionalProperties:settings at #->properties:properties->additionalProperties:blocks->properties:items->anyOf[0]
      1: Array expected, {"$ref":"file:\/\/block.json"} received at #->properties:properties->additionalProperties:blocks->properties:items->anyOf[1]
    } at #->properties:properties->additionalProperties:blocks->properties:items
    

    Can we bind the error to the JSON data instead of the JSON schema. I need to present the which property is missing, or invalid, our customer will be insane if we show the error above. Even me , unable to figure out what the JSON data is wrong from the error above. please let me know if this is possible please at the moment.

    opened by videni 1
  • Is it possible to deactivate $ref in the output?

    Is it possible to deactivate $ref in the output?

    We have classes with oneOf and discriminator (type attribute). When exporting an object there are "$ref" in the output which is unwanted for us. Question would be if that could be deactivated, if that should be like that or if we are using the library in a wrong way. We are not seeing "$ref" if we input stdClasses that do not extend ClassStructure.

    I have attached a minimal example that shows the behaviour. I'm happy to answer more questions.

    <?php
    require_once __DIR__ . '/vendor/autoload.php';
    
    use Swaggest\JsonSchema\Schema;
    use Swaggest\JsonSchema\Structure\ClassStructure;
    
    class Order extends ClassStructure {
        public $items;
    
        /**
         * @param Properties|static $properties
         * @param Schema $ownerSchema
         */
        public static function setUpProperties($properties, Schema $ownerSchema)
        {
            $properties->items               = Schema::arr();
            $properties->items->items        = new Schema();
    		$properties->items->items->oneOf = [
    			AOrderItem::schema(),
    			BOrderItem::schema(),
    		];
            $ownerSchema->type = 'object';
            $ownerSchema->required = [
                self::names()->items
            ];
        }
    }
    
    class OrderItem extends ClassStructure {
        public $type;
    
        /**
         * @param Properties|static $properties
         * @param Schema $ownerSchema
         */
        public static function setUpProperties($properties, Schema $ownerSchema)
        {
            $properties->type = Schema::string();
            $ownerSchema->type = 'object';
            $ownerSchema->required = [
                self::names()->type
            ];
        }
    }
    
    class AOrderItem extends OrderItem {
        public $typeSpecific;
    
        /**
         * @param Properties|static $properties
         * @param Schema $ownerSchema
         */
        public static function setUpProperties($properties, Schema $ownerSchema)
        {
            parent::setUpProperties($properties, $ownerSchema);
            $properties->typeSpecific = ATypeSpecific::schema();
            $ownerSchema->required[] = self::names()->typeSpecific;
        }
    }
    
    class BOrderItem extends OrderItem {
        public $typeSpecific;
    
        /**
         * @param Properties|static $properties
         * @param Schema $ownerSchema
         */
        public static function setUpProperties($properties, Schema $ownerSchema)
        {
            parent::setUpProperties($properties, $ownerSchema);
            $properties->typeSpecific = BTypeSpecific::schema();
            $ownerSchema->required[] = self::names()->typeSpecific;
        }
    }
    
    class ATypeSpecific extends ClassStructure {
        public $a;
    
        /**
         * @param Properties|static $properties
         * @param Schema $ownerSchema
         */
        public static function setUpProperties($properties, Schema $ownerSchema)
        {
            $properties->a = Schema::string();
            $ownerSchema->type = 'object';
            $ownerSchema->required = [
                self::names()->a
            ];
        }
    }
    
    class BTypeSpecific extends ClassStructure {
        public $b;
    
        /**
         * @param Properties|static $properties
         * @param Schema $ownerSchema
         */
        public static function setUpProperties($properties, Schema $ownerSchema)
        {
            $properties->b = Schema::integer();
            $ownerSchema->type = 'object';
            $ownerSchema->required = [
                self::names()->b
            ];
        }
    }
    
    $ts = new BTypeSpecific();
    $ts->b = 1;
    $b = new BOrderItem();
    $b->type = 'b';
    $b->typeSpecific = $ts;
    $order = new Order();
    $order->items = [$b];
    
    echo json_encode(Order::export($order), JSON_PRETTY_PRINT);
    
    opened by glamic-ep 0
Releases(v0.12.41)
Owner
Tools to manage schemas and generated code
null
A Magento implementation for validating JSON Structures against a given Schema

Zepgram JsonSchema A Magento implementation for validating JSON Structures against a given Schema with support for Schemas of Draft-3 or Draft-4. Base

Benjamin Calef 1 Nov 5, 2021
JSON schema models and generated code to validate and handle various data in PocketMine-MP

DataModels JSON schema models and generated code to validate and handle various data in PocketMine-MP This library uses php-json-schema-model-generato

PMMP 2 Nov 9, 2022
Allows generate class files parse from json and map json to php object, including multi-level and complex objects;

nixihz/php-object Allows generate class files parse from json and map json to php object, including multi-level and complex objects; Installation You

zhixin 2 Sep 9, 2022
A PHP 7.4+ library to consume the Confluent Schema Registry REST API

A PHP 7.4+ library to consume the Confluent Schema Registry REST API. It provides low level functions to create PSR-7 compliant requests that can be used as well as high level abstractions to ease developer experience.

Flix.TECH 38 Sep 1, 2022
Php-rpc-server - JSON RPC server implementation for PHP.

JSON RPC Server implementation for PHP. The json-rpc is a very simple protocol. You can see this by reading the protocol specification. This library i

null 4 Sep 28, 2022
Json-normalizer: Provides generic and vendor-specific normalizers for normalizing JSON documents

json-normalizer Provides generic and vendor-specific normalizers for normalizing JSON documents. Installation Run $ composer require ergebnis/json-nor

null 64 Dec 31, 2022
PDF API. JSON to PDF. PDF Template Management, Visual HTML Template Editor and API to render PDFS by json data

PDF Template Management, Visual HTML Template Editor and API to render PDFS by json data PDF ENGINE VERSION: development: This is a prerelease version

Ajous Solutions 2 Dec 30, 2022
Zilliqa PHP is a typed PHP-7.1+ interface to Zilliqa JSON-RPC API.

Zilliqa PHP is a typed PHP-7.1+ interface to Zilliqa JSON-RPC API. Check out the latest API documentation. Add library in a composer.json file.

Patrick Falize 6 Oct 7, 2021
JSON Lint for PHP

JSON Lint Usage use Seld\JsonLint\JsonParser; $parser = new JsonParser(); // returns null if it's valid json, or a ParsingException object. $parser-

Jordi Boggiano 1.3k Dec 26, 2022
Simple game server with php without socket programming. Uses the Api request post(json).

QMA server Simple game server with php without socket programming. Uses the Api request post(json). What does this code do? Register the user as a gue

reza malekpour 3 Sep 4, 2021
Learning about - Basic HTML & CSS, JSON, XML, Session & Cookies, CRUD Operations in Php using MySQL and Create MVC from scratch

This Project is based on course CSC 3215. Learning about - Basic HTML & CSS, JSON, XML, Session & Cookies, CRUD Operations in Php using MySQL and Create MVC (Model–View–Controller) from scratch. Just learning about web technologies, Not focusing on UI (Bootstrap or other 3rd-Party UI libraries or frameworks).

Alvi Hasan 5 Sep 21, 2022
Declaratively specify how to extract elements from a JSON document, in PHP

jmespath.php JMESPath (pronounced "jaymz path") allows you to declaratively specify how to extract elements from a JSON document. jmespath.php allows

null 1.7k Dec 30, 2022
Dependency graph visualization for composer.json (PHP + Composer)

clue/graph-composer Graph visualization for your project's composer.json and its dependencies: Table of contents Usage graph-composer show graph-compo

Christian Lück 797 Jan 5, 2023
JsonQ is a simple, elegant PHP package to Query over any type of JSON Data

php-jsonq JsonQ is a simple, elegant PHP package to Query over any type of JSON Data. It'll make your life easier by giving the flavour of an ORM-like

Nahid Bin Azhar 834 Dec 25, 2022
The main website source code based on php , html/css/js and an independent db system using xml/json.

jsm33t.com Well umm, a neat website LIVE SITE » View Demo · Report Bug · Request a feature About The Project Desc.. Built Using Php UI Frameworks Boot

Jasmeet Singh 5 Nov 23, 2022
This is a library to serialize PHP variables in JSON format

This is a library to serialize PHP variables in JSON format. It is similar of the serialize() function in PHP, but the output is a string JSON encoded. You can also unserialize the JSON generated by this tool and have you PHP content back.

Zumba 118 Dec 12, 2022
World countries - available in multiple languages, in CSV, JSON, PHP, SQL and XML formats

Constantly updated lists of world countries and their associated alpha-2, alpha-3 and numeric country codes as defined by the ISO 3166 standard, available in CSV, JSON , PHP, SQL and XML formats, in multiple languages and with national flags included; also available are the ISO 3166-2 codes of provinces/ states associated with the countries

Stefan Gabos 1k Dec 29, 2022
A pure PHP implementation of the MessagePack serialization format / msgpack.org[PHP]

msgpack.php A pure PHP implementation of the MessagePack serialization format. Features Fully compliant with the latest MessagePack specification, inc

Eugene Leonovich 368 Dec 19, 2022
A pure PHP implementation of the open Language Server Protocol. Provides static code analysis for PHP for any IDE.

A pure PHP implementation of the open Language Server Protocol. Provides static code analysis for PHP for any IDE.

Felix Becker 1.1k Jan 4, 2023