Giter Site home page Giter Site logo

json-schema's People

Contributors

1ma avatar atalargo avatar clivern avatar ed-leroux avatar hkarlstrom avatar kodywiremane avatar liborm85 avatar maspeng avatar msarca avatar nickdnk avatar niconoe- avatar sorinsarca avatar tagliatti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

json-schema's Issues

Empty array from dataPointer()?

When I call $error->dataPointer(), I get an empty array. What is it supposed to return? It says "Returns array|string[]|int[]" but I'm not sure what that means I should expect. I was expecting a string with a json pointer. I have many errors in my schema but they all just give empty arrays when I call that function.

Error when validating array of objects

Hi, im trying to validate this data:

{
    "content": [
        {
            "type": "text",
            "content": "hello world"
        }
    ]
}

With this schema (PHP):

public static $schema = [
        "type" => "object",
        "properties" => [
            "content" => [
                "type" => "array",
                "minItems" => 1,
                "items" => [
                    "type" => "object",
                    "properties" => [
                        "type" => [
                            "enum" => ["text", "image"]
                        ],
                        "content" => [
                            "type" => "string"
                        ],                        
                    ],                    
                ]
            ]
        ],
        "additionalProperties" => false
    ];

It display many warnings:

Warning: get_object_vars() expects parameter 1 to be object, array given in \vendor\opis\json-schema\src\Validator.php on line 1793

Warning: array_keys() expects parameter 1 to be array, null given in \vendor\opis\json-schema\src\Validator.php on line 1793

Warning: array_map(): Expected parameter 2 to be an array, null given in \vendor\opis\json-schema\src\Validator.php on line 1793

Warning: First parameter must either be an object or the name of an existing class in \vendor\opis\json-schema\src\Validator.php on line 1914

Warning: First parameter must either be an object or the name of an existing class in \vendor\opis\json-schema\src\Validator.php on line 1914

Is it a bug?
What i'm doing wrong?

PD: I'm using 1.0.19 version

Can't handle validating an object property called "default"

If a schema of type object specifies a property called default, refs cannot be resolved. This is present in the latest master commit (1986851).

Here's a minimal schema to demonstrate this behavior:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://test.test/data/schema/test.json",
  "title": "Test schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1
    },
    "identifier": {
      "$ref": "types/identifier.json"
    },
    "default": {
      "$ref": "types/default.json"
    }
  }
}

This will validate against a valid file if the default property's constraints are inlined. However, with the ref, it will fail with Opis\JsonSchema\Exception\SchemaNotFoundException : Schema 'types/default.json' was not found or could not be loaded.

In both cases, the ref to types/identifier.json is resolved properly. Changing the name of the default property to is_default works around this problem, but I'd rather not have to change the data format because of this.

I can't find anything in the JSON schema draft-07 spec that says default is not a valid property name, although maybe I'm missing something?

Loader not restored in Validator::schemaValidation() if exception thrown

Validator objects look like they supposed to be reusable. Validator::schemaValidation() temporarily saves a current loader, and restores it after calling validateSchema() with a loader from the arguments.

But, if an exception is thrown under validateSchema() (caused by an invalid schema, for example), which is not fatal by itself, schemaValidation() aborts before it can restore the saved loader.

All-in-one test mass
<?php

require_once __DIR__.'/vendor/autoload.php';

use Opis\JsonSchema\ISchemaLoader;
use Opis\JsonSchema\Schema;
use Opis\JsonSchema\Validator;
use Opis\JsonSchema\Exception\AbstractSchemaException;

class TestLoader implements ISchemaLoader
{
    protected $tag;
    public function __construct(string $tag)
    {
        $this->tag = $tag;
    }
    public function tag() : string
    {
        return $this->tag;
    }
    public function loadSchema(string $uri)
    {
        // not actually called
        return new Schema(true);
    }
}

$goodLoader = new TestLoader("good");
$badLoader = new TestLoader("BAD");

$goodSchema = new Schema(true, "Good Schema");
$badSchema = new Schema((object)["type" => "}:3"], "Bad Schema");

$dummyData = new stdClass();

$dataFlow = [
    [$dummyData, $goodSchema],
    [$dummyData, $badSchema],
    [$dummyData, $goodSchema]
];

print("Create validator...\n");
$validator = new Validator(null, $goodLoader);
print("Default loader is " . $validator->getLoader()->tag() . "\n");

foreach($dataFlow as $dataPacket) {

    $data = $dataPacket[0];
    $schema = $dataPacket[1];
    $schemaId = $schema->id();

    print("Validating with $schemaId and bad loader...\n");

    try {
        $validator->schemaValidation($data, $schema, 1, $badLoader);
    } catch (AbstractSchemaException $e) {
        $msg = $e->getMessage();
        print("Exception caught: $msg\n");
    }

    $loaderTag = $validator->getLoader()->tag();
    print("Loader is $loaderTag outside\n");

}

/* Output:
Create validator...
Default loader is good
Validating with /Good Schema# and bad loader...
Loader is good outside
Validating with /Bad Schema# and bad loader...
Exception caught: 'type' keyword contains unknown value: }:3
Loader is BAD outside
Validating with /Good Schema# and bad loader...
Loader is BAD outside
*/

Passing the temporary loader through to validateSchema() seems complicated due to the method being called in about 20 different places, while it is its sub-callee validateRef() that relies on $this->loader. So I suggest simply modifying this fragment

$bag = new ValidationResult($max_errors);
$this->validateSchema($data, $data, [], [], $schema, $schema->resolve(), $bag);
$this->loader = $default_loader;
return $bag;

to

$bag = new ValidationResult($max_errors);
try {
    $this->validateSchema($data, $data, [], [], $schema, $schema->resolve(), $bag);
} finally {
    $this->loader = $default_loader;
}
return $bag;

Not sure where to put the test, if adding it to the test suite. Maybe BasicTest.php.

This report isn't too wordy, is it?

$validator->totalErrors()

$validator->totalErrors() ever return 1 if has error
If you have more than one error it returns 1 anyway, you can not get all errors ...

Implement JsonSerializable on ValidationError

I would like to send the validation errors as a json response, so I can use them directly to display form errors in the frontend.

The easiest I can think of is to implement JsonSerializable on ValidationError.
If you're ok with it, I could provide a PR.

Or is there an easier/better way to do it?

Basic example is not working

As taken from readme:

$validator = new Validator();
$schema = (object) ['minLength' => 3];
$validator->dataValidation("abc", $schema);
PHP Fatal error:  Uncaught Opis\JsonSchema\Exception\InvalidSchemaException: Schema must be an object or a boolean, NULL given in /home/etki/Workspace/ama-team.github.io/php/elasticsearch-schema/vendor/opis/json-schema/src/Validator.php:295
Stack trace:
#0 /home/etki/Workspace/ama-team.github.io/php/elasticsearch-schema/vendor/opis/json-schema/src/Validator.php(87): Opis\JsonSchema\Validator->validateSchema('abc', 'abc', Array, Array, Object(Opis\JsonSchema\Schema), NULL, Object(Opis\JsonSchema\ValidationResult))
#1 /home/etki/Workspace/ama-team.github.io/php/elasticsearch-schema/vendor/opis/json-schema/src/Validator.php(107): Opis\JsonSchema\Validator->schemaValidation('abc', Object(Opis\JsonSchema\Schema), 1, NULL)

Fast debugging has shown that $schema->resolve() returns null because schema->id was set to urn:5a4509dde8a0f#, but the required reference in schema->internal was urn:/5a4509dde8a0f# (had extra slash).

URI regular expression does not match UTF-8 characters

If I use this schema:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "url": {
            "type": "string",
            "format": "uri"
        }
    }
}

And this json:

{
    "url":"http://www.test.com/tést"
}

... the validation will fail due to the "format" rule.

I believe this is because the regex isValidPath uses (in Opis\JsonSchema\URI) doesn't test for UTF-8 characters.

Instead of this:

/^(?:(%[0-9a-f]{2})|[a-z0-9\/:@\-._~\!\$&\'\(\)*+,;=])*$/i

it should be this:

/^(?:(%[0-9a-f]{2})|[\p{L}0-9\/:@\-._~\!\$&\'\(\)*+,;=])*$/ui

It's likely the other expressions would need the same update.

Can't load schema

Hi I'm trying to use the library to load an schema with reference to definitions in other file.
I have two .json files located in http://localhost/opis/

schema.json:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Controller validator",
  "description": "Controller",
  "$id": "http://localhost/opis/schema.json",
  "type": "object",
  "properties": {
    "controller": {
      "$ref": "referenced.json#/definitions/controller"
    }
  }
}

and referenced.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Controller",
  "description": "Controller",
  "$id": "http://localhost/opis/referenced.json",
  "definitions": {
    "controller": {
      "type": "object",
      "properties": {
        "deviceid": {
          "description": "Unique MAC id of the controller",
          "type": "string",
          "minLength": 17,
          "maxLength": 17,
          "pattern": "^([0-F|a-f]{2}\\:){5}[0-9|a-f|A-F]{2}"
        },
        "boxid": {
          "description": "Unique box ID",
          "type": "string",
          "minLength": 6,
          "maxLength": 11,
          "pattern": "demo-.*",
          "examples": [
            "demo-123456"
          ]
        }
      },
      "required": [
        "deviceid"
      ]
    }
  }
}

My code looks like this:

public static function compareJsonSchema($args){

        switch ($args['method']) {
            case "POST":
                $data = $args['config_json'];
                
                $loader = new \Opis\JsonSchema\Loaders\File('http://localhost/', ['/opis',]);
                $schema = $loader->loadSchema('http://localhost/schema.json');

                $validator = new Validator(null, $loader);

                $result = $validator->schemaValidation($data, $schema);

                if ( $result->isValid() ) {
                    return ['status' => 200, 'message' => 'POST working correctly and is valid '. $result->isValid()];
                } else {
                    $error = $result->getFirstError();
                    return ['status' => 200, 'message' => $error->keyword() . ' : ' . json_encode($error->keywordArgs(), JSON_PRETTY_PRINT)];
                }

        }
        return [
            'status' => 403,
            'message' => 'compareJsonSchema ERROR: Invalid method or url',
        ];
    }

Problem is, I cant seem to get loadSchema to return the schema, but returns null every time and validator needs the ISchema object.
If I use file_get_contents() on the route I can get the .json files with no problem, so its not a matter of file permissions.

Been stuck for a couple days with this and cant seem to figure it out.

Combination of allOf and if/then not working

Hi,

I'm using a combination of allOf and if/then, to do some conditional validations.

In my example, a dailyWinnersMessage is only required, if the number of dailyWinners is not 0.
If I set dailyWinners to 1 and leave the dailyWinnersMessage empty, it does not result in an error.
Same for the other 3 conditions.

I tried it on https://www.jsonschemavalidator.net/ and it worked there, so (without deep knowledge of json schema validation) I assume this is something that should be possible with draft 7.

Correct me if I'm wrong or if there's a better way to do this with the current implementation.

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "CreateRaffle",
    "title": "Create Raffle",
    "description": "A request body to create a raffle",
    "type": "object",
    "properties": {
        "name": {
            "description": "The name of the raffle",
            "type": "string",
            "minLength": 1,
            "maxLength": 255
        },
        "startTime": {
            "description": "The start time of the raffle",
            "type": "string",
            "format": "date-time"
        },
        "endTime": {
            "description": "The end time of the raffle",
            "type": "string",
            "format": "date-time"
        },
        "dailyWinners": {
            "description": "The number of daily winners",
            "type": "integer"
        },
        "dailyWinnersMessage": {
            "description": "The message for a daily winner",
            "type": "string"
        },
        "weeklyWinners": {
            "description": "The number of daily winners",
            "type": "integer"
        },
        "weeklyWinnersMessage": {
            "description": "The message for a weekly winner",
            "type": "string"
        },
        "monthlyWinners": {
            "description": "The number of monthly winners",
            "type": "integer"
        },
        "monthlyWinnersMessage": {
            "description": "The message for a monthly winner",
            "type": "string"
        },
        "firstPriceWinners": {
            "description": "The number of first price winners",
            "type": "integer"
        },
        "firstPriceWinnersMessage": {
            "description": "The message for a first price winner",
            "type": "string"
        },
        "tryAgainMessage": {
            "description": "The message for a loser",
            "type": "string",
            "minLength": 1
        },
        "alreadyParticipatedMessage": {
            "description": "The message for someone who already participated",
            "type": "string",
            "minLength": 1
        },
        "winnerPickingStrategy": {
            "description": "The winner picking strategy",
            "type": "string",
            "enum": [
                "instantly"
            ]
        }
    },
    "allOf": [
        {
            "if": {
                "properties": {
                    "dailyWinners": {
                        "not": {
                            "enum": [0]
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "dailyWinnersMessage": {
                        "type": "string",
                        "minLength": 1
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "weeklyWinners": {
                        "not": {
                            "enum": [0]
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "weeklyWinnersMessage": {
                        "type": "string",
                        "minLength": 1
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "monthlyWinners": {
                        "not": {
                            "enum": [0]
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "monthlyWinnersMessage": {
                        "type": "string",
                        "minLength": 1
                    }
                }
            }
        },
        {
            "if": {
                "properties": {
                    "firstPriceWinners": {
                        "not": {
                            "enum": [0]
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "firstPriceWinnersMessage": {
                        "type": "string",
                        "minLength": 1
                    }
                }
            }
        }
    ],
    "required": [
        "name",
        "startTime",
        "endTime",
        "dailyWinners",
        "dailyWinnersMessage",
        "weeklyWinners",
        "weeklyWinnersMessage",
        "monthlyWinners",
        "monthlyWinnersMessage",
        "firstPriceWinners",
        "firstPriceWinnersMessage",
        "tryAgainMessage",
        "alreadyParticipatedMessage",
        "winnerPickingStrategy"
    ]
}

Thanks and regards - Steffen

bug

Simple example.

<?php
require 'vendor/autoload.php';

use Opis\JsonSchema\{
    Validator,
    ValidationResult,
    ValidationError
};

$validator = new Validator();

$schema = <<<'JSON'
{
	"type": "object"
}
JSON;
	
$result = $validator->dataValidation('{}', $schema);

if ($result->isValid()) {
    echo "Valid", PHP_EOL;
}
else {
    /** @var ValidationError $error */   
    var_dump($result->getErrors());
}
?>

Response:

array(1) { 
[0]=> object(Opis\JsonSchema\ValidationError)#9 (6) {
	["data":protected]=> string(2) "{}"
	["dataPointer":protected]=> array(0) { }
	["schema":protected]=> object(stdClass)#7 (4) {
		["type"]=> string(6) "object"
		["$schema"]=> string(39) "http://json-schema.org/draft-07/schema#"
		["$id"]=> string(30) "json-schema-id:/5b22621c76688#"
		["$_base_id"]=> string(30) "json-schema-id:/5b22621c76688#" }
		["keyword":protected]=> string(4) "type"
		["keywordArgs":protected]=> array(2) {
			["expected"]=> string(6) "object"
			["used"]=> string(6) "string"
			}
		["subErrors":protected]=> array(0) { }
		}
	}

AllOf keyword stops on failure

Using the AllOf keyword validates all schemas in the array however if an item in that array causes a failure the remaining items are not processed, from a programming perspective this seems efficient but the output seems to assert that some of the schema is correct.

Recreation
Using the following schema as an example;

{
  "allOf": [
    {
      "required": [
        "name"
      ],
      "properties": {
        "name": {
          "type": "string"
        }
      }
    },
    {
      "required": [
        "email"
      ],
      "properties": {
        "name": {
          "type": "string"
        }
      }
    }
  ]
}

And using the following inputs, this behaviour can be seen.

{
	"name": "example"
}

// One "allOf" error with one required sub-error for "email" - expected behaviour.
{
	"email": "example"
}

// One "allOf" error with one required sub-error for "name" - expected behaviour.
{

}
// One "allOf" error with one required sub-error for "name" - unexpected behaviour.

The last example should generate a single "allOf" error but with two required sub-errors for "name" and "email".

Looking at the validator this behaviour can be corrected by changing a break; for a continue; in the "allOf' validation.

src/Validator.php - line 902

            foreach ($schema->allOf as &$one) {
                if (!is_bool($one) && !is_object($one)) {
                    throw new SchemaKeywordException(
                        $schema,
                        'allOf',
                        $schema->allOf,
                        "'allOf' keyword items must be booleans or objects, found " . gettype($one)
                    );
                }
                if (!$this->validateSchema($document_data, $data, $data_pointer, $parent_data_pointer, $document, $one, $newbag)) {
                    $valid = false;
                    $errors = $newbag->getErrors();
--                    break;
++                    continue;
                }
                $newbag->clear();
            }

Validate assoc array data

Is it possible to validate associative array's instead of objects as the data being validated?

I don't want to resolve to something like the below "hack" (or walking through the array to cast every child (associative) array):

$assoc = [
    'foobar' => 'test',
    [
        [
            'id'   => 100,
            'type' => 'foo',
            'name' => 'random',
        ],
        [
            'id'   => 1231230,
            'type' => 'faa',
            'name' => 'randomasdasdasd',
        ],
    ],
];
$data   = json_decode(json_encode($assoc));

update data with default values from schema

will this be supported in future versions?
I'm looking for ajv functionality for this subject in php also. this will make post validation processing easier.

if you/anyone know of a solution, I'll be happy to hear.

Best regards,
Amit

"type": "string" allows non-printable characters

So I am using json schema to validate user input against an API and some users managed to sneak in this:

"street_name": "SALVADOR D\u00c1\bL\u00cd",

It is a non-printable control character (backspace), \bL, that creates the issue because down the pipe I am using this data to create xml documents (using twig, yeah I know XSLT would be better but the xml template is hand written and only the json schema is rigid).

So can I tell opis to set the type string to only allow printable characters? I know I can create a format and I did so for iban, imei, email etc. but I want to apply it to all types of string because for this API any string input can only be valid if it is printable characters (and that is the case not with all but most JSON APIs).

So any way to overwrite the default type checker?

$ref only supported at the root?

I may be missing something, but it appears that $ref is only recognized if it's at the root of the schema document. I tried following the example given in the documentation, but the $ref isn't even detected.

(When I move the $ref property to the root, it's detected, but it fails to load if ISchemaLoader isn't provided to Validator. Perhaps more an issue with documentation than the software itself.)

Array type validation request

Any chance you'll support schema validation for arrays? I'm looking for a way to validate the items in an array match my schema. Thanks in advance!

e.g.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "FIELD1": {
      "type": ["integer", "string", "null"]
    },
    "ITEMS": {
      "type": "array",
      "properties": {
        "ID": {
          "type": "string"
        },
        "EMP_NUMBER": {
          "type": ["integer", "string", "null"]
        }
      },
      "required": [
        "ID", "EMP_NUMBER"
      ],
      "additionalProperties": false
  }
},
  "required": [
    "FIELD1","ITEMS"
],
  "additionalProperties": false
}

Treat iterable object as array

We are using json schema to validate deserialized objects (processed by Symfony serializer) and some arrays are deserialized into collection classes. Validator treats such collections as an object https://github.com/opis/json-schema/blob/master/src/ValidatorHelper.php#L70. We are suggesting to add ability to treat iterable object as array.

if (is_iterable($value)) {
    $ok = true;
    $index = 0;
    foreach ($value as $i => $v) {
        if ($index !== $i) {
            $ok = false;
            break;
        }

        $index++;
    }
    if ($ok) {
        return "array";
    }
}

Error message

Sorry for opening another error, but I'll close, is there any way to set a custom error message for the parameter? Because I wanted to send the message to the user, for example:

{
"type": "string",
"error_msg": "Invalid name !!"
}

And somehow if you do not validate give to get in php and send the message to the user.

Error messages

I'm writing this because there is a demand for error messages (see #14 and #15) and I'm not sure
which way is the best. There are multiple ways of handling error messages and I'm pretty sure that they are different enough to require different implementations, and I need some feedback.

1)

One way is the json-guard's approach - a flat array of errors. But this approach is not so verbose, for example the anyOf keyword says that no schema is matched but it doesn't say why every schema failed - because of a minimum keyword? or maybe a type keyword?

Schema

{
  "anyOf": [
      {"type": "string"},
      {"type": "number", "minimum": 10}
  ]
}

Data

5

Errors reported by json-guard

[
   {
       "keyword": "anyOf",
       "message": "The data must match one of the schemas.",
      // ...
   }
]

2)

The second way will be similar to json-guard for simple keywords, but the errors for keywords like anyOf will also contain nested arrays of sub-errors.

Here is an example (consider unlimited errors allowed):

Schema

{
    "allOf": [
        {"type": "number"},
        {"$ref": "#/anyOf/1"},
        {"multipleOf": 2}
    ],
    "anyOf": [
        {"type": "string"},
        {"minimum": 11}
    ]
}

Errors when data is 9

[
    {
        "keyword": "allOf",
        "message": "The data must match all subschemas",
        "data": 9,
        "dataPath": [],
        // ...other optional properties (see below)
        "subErrors": [
            {
                "keyword": "minimum",
                "message": "The number must be greater than or equal to 11",
                "data": 9,
                "dataPath": [],
                // ... other optional properties, but no subErrors
            }
        ]
    },
    {
        "keyword": "anyOf",
        "message": "The data must match at least one subschema",
        "data": 9,
        "dataPath": [],
        // ...
        "subErrors": [
            {
                "keyword": "type",
                "message": "The data type must be string",
                "data": 9,
                "dataPath": [],
                // ...
            },
            {
                "keyword": "minimum",
                "message": "The number must be greater than or equal to 11",
                "data": 9,
                "dataPath": [],
                // ...
            }
        ]
    }
]

Other optional properties can be:

  • schema - object/boolean
  • schemaPath - path to the schema
  • schemaId
  • others

3)

In the previous approach (2) the problem is that you cannot assign to a schema an error and treat that schema like a simple keyword (some kind of atomicity). Here is what I mean:

{
    "properties": {
        "d": {"$ref": "#/definitions/digit"}
    },

    "definitions": {
        "digit": {
           "type": "integer",
           "minimum": 0,
           "maximum": 9
        }
    }
}

Data

{"d": 12}

Desired error:

{
    "data": 12,
    "dataPointer": ["d"],
    "message": "The value must be a digit",
    // ...
}

As you can see, I don't care how the "digit" schema validates the data, I'm interested only in the result, therefore I want a custom message.

We can add some metadata keywords to solve the problems of the previous approach.

{
    "properties": {
        "d": {"$ref": "#/definitions/digit"}
    },

    "definitions": {
        "digit": {

           "$error": "The value must be a digit",

           "type": "integer",
           "minimum": 0,
           "maximum": 9
        }
    }
}

Now, when the $ref fails to validate the data, it will check for$error keyword (in the referenced schema) and use the custom message if exists.

4)

There is always room for extending so this approach will tackle the problems for keywords like pattern.

Here is an example of what I mean:

Schema

{
   "maxLength": 20,
   "pattern": "^[A-Z].*"
}

Data

"abc"

Desired error:

{
    "data": "abc",
    "message": "The string must start with a capital letter",
    // ...
}

Current/Default error:

{
    "data": "abc",
    "message": "The string must match pattern ^[A-Z].*",
    // ...
}

Again, some metadata keyword can be used to replace the default message.

{
   "maxLength": 20,
   "pattern": "^[A-Z].*",

   "$errors": {
     "maxLength": "Too long! 20 chars should be enough for everyone",
     "pattern": "The string must start with a capital letter"
   }
}

These are the cases that have come to my mind at the moment, probably there are many more.

Required in a schema loaded with $ref

Hello,

I am new in JSON Schemas and I don't know how to implement this: I have a schema that is loaded in other schemas, but I want to do some properties required in one schema and other properties required in other schema. I mean:

`
{

"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "file:///path/to/schema/test.json#",
"type": "object",
"additionalProperties":  false,
"properties": {
    "name": {
        "$id": "#/properties/name",
        "type": "string"
    },
    "type": {
        "$id": "#/properties/type",
        "type": "integer"
    }
}

}
`

Here I want to use test.json but with required (it doesn't work):

`
{

"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "file:///path/to/schema/testRequired.json#",

"required": ["name", "type"],
"test": {"$ref": "file:///path/to/schema/test.json#"},

}
`

I don't want to use required in test.json because I will use test.json without required in other schemas.

Thank you

Selecting from schema

In Graphql, you can define a schema and then using a Graphql query you can select parts of that schema to utilise, like how in SQL you have a schema and then a query of SELECT a, b doesn't have to talk about c even if c is in the table

Is it possible to have a selecting mechanism in json-schema? so that I can define a single schema and then at different call points use different selections of the same schema?
Ideally in some kind of DSL so that it can be easily shown to other users in terms of what might be expected of them

Possible to use schema in Swagger-UI

Hi,

We are using zircote/zwagger for our API documentation, works good! Next thing we would like to do, is including our json data schemas using ref. It does something, but not completely correct. For example it shows the "additionalProperties" key which ofcourse only is used for validation and is not a real-input value.

So it works partly, is this possible at all?

Using this very simple scheme to test with:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "http://api.example.com/profile.json#",
    "type": "object",
    "properties": {
            "username": {
                "type": "integer",
                "minimum": 1
            },
            "expires": {
                "type": "integer",
                 "minimum": 1,
                 "maximum": 100
            }
    },
    "required": ["username"],
    "additionalProperties": false
}

Default value breaks oneOf matching

Default values are set before matching a oneOf schema. This can cause the default value of one schema to stop the data matching another schema in the array.

eg

use Opis\JsonSchema\ValidationError;
use Opis\JsonSchema\Validator;
use Opis\JsonSchema\Schema;

$json = <<<'JSON'
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": [
    "thing"
  ],
  "properties": {
    "thing": {
      "oneOf": [
        {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "bad_property": {
              "type": "string",
              "default": "I break validation"
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "good_property": {
              "type": "string"
            }
          }
        }
      ]
    }
  }
}
JSON;


$data = (object)[
    'thing' => (object) [
        'good_property' => 'should be good',
    ],
];
$opisSchema = Schema::fromJsonString($json);

$opisValidator = new Validator();

$result = $opisValidator->schemaValidation($data, $opisSchema, -1);

if ($result->isValid()) {
    echo '$data is valid', PHP_EOL;
} else {
    /** @var ValidationError $error */
    foreach ($result->getErrors() as $error) {
        echo '$data is invalid', PHP_EOL;
        echo 'Error: ', $error->keyword(), PHP_EOL;
        echo json_encode($error->keywordArgs(), JSON_PRETTY_PRINT), PHP_EOL;
    }
}

The above generates the following:

$data is invalid
Error: oneOf
{
    "matched": 0
}

The data validates fine as soon as you remove the default property from the schema.

Pretty easy to work around for now by avoiding default values.

External Schema returning null

Hey there! I'm having an issue getting my schemas files, which are hosted on a public AWS S3 bucket, to be recognized by uriValidation.

Note:

  1. I've tried reinstalling opis/json-schema
  2. I've looked at the other closed issues and those did not help either..
SCHEMA: test.json

{
    "$id": "https://BUCKET_NAME.s3.amazonaws.com/schemas/test.json",
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "CNAME": {
            "type": "string"
        },
        "EMAIL": {
            "type": "string"
        }
    },
    "required": ["CNAME", "EMAIL"],
    "additionalProperties": true
}
PHP FILE:

$loader = new \Opis\JsonSchema\Loaders\File('http://BUCKET_NAME.s3.amazonaws.com/', ['schemas/']);

$validator = new Validator(null, $loader);

$data = json_decode(file_get_contents(__DIR__.'/data/test_data.json'), false);

$result = $validator->uriValidation($data, "http://BUCKET_NAME.s3.amazonaws.com/schemas/test.json");

if ($result->isValid()) {
    // Valid Data
} else {
   // Invalid Data

Class UnicodeString doesn't exist

Hello,

In /src/ValidatorHelper.php file, in __construct method there is this line:
class_exists('\\Opis\String\\UnicodeString', true).

This line produces a warning in system.log in a Magento system.
Not a big deal but I was wondering if this line can be removed since I can't see any reference regarding UnicodeString.

Thank you

Error Path, Error Message, and Unlimited Max Errors

Not sure if I should separate these issues, but I can if needed.

  1. Error Message & Error Path
    The current structure for error response varies pretty widely dependent on the error. Is there anyway I can just get the key path that the error occurred and an error message? (like the python implementation of json-schema) If that's not possible, are there any docs on all the possible error response structures? Example:
/*schema being used*/
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "/API/V1/Login/schemas/post.json",
    "type" : "object",
    "properties" : {
        "email" : {
            "type" : "string",
            "format" : "email"
        },
        "password" : {
            "type": "string",
            "minLength": 6
        }
    },
    "required" : ["email", "password"],
    "additionalProperties" : false
}
/*bad request recieved*/
{
  "password": 1
}
/*library error response using all the available methods on ValidationError class*/
[
   {
      "pointer": [],
      "keyword": "required",
      "arguments": {
        "missing": "email"
      }
    },
    {
      "pointer": [
        "password"
      ],
      "keyword": "type",
      "arguments": {
        "expected": "string",
        "used": "integer"
      }
    }
]
/* python style implementation (what I am trying to get) 
ValidationError class has two more methods (getErrorPath(), getErrorMessage()) */
[
    {
      "path": "email",
      "message": "missing required field"
    },
    {
      "path": "password",
      "message": "type must be a `string`"
    }
]    
  1. Unlimited Errors
    I see the default for $max_errors is set to 1; how can I set max errors to unlimited? I've tried to just not pass anything (it defaults to 1) and tried setting to null (it throws an error).

Lack of error messages hampers adoption

We're currently using json-guard to validate incoming API requests. The validation errors it produces are developer friendly and can be localized. You can immediately use them as part of the API error response.

Unfortunately json-guard is abandoned and we're looking to switch to a different library. justinrainbow/json-schema is a hot mess, so that leaves us with swaggest/json-schema and opis/json-schema. Both the swaggest and opis libraries are lacking developer friendly error messages.

I read the conversation in #11 and thought about implementing a formatter/renderer as suggested. Unfortunately, to do so I pretty much have to go though the entire spec and test each possible error to see how it looks in opis/json-schema. For someone who wants to consume a library, this requires intimate knowledge of the library internals.

So IMO this library is incomplete/unusable until error messages are added and most likely other people will decide to stay away from it as well. Hence the issue title. As for us, we will keep using json-guard for now.

'min' keyword arg passed to the error constructor for 'maxProperties' validation

Hello,

as of commit f99e244, and still in 91f2464, in src/Validator.php, line 1848, I see the following:

$bag->addError(new ValidationError($data, $data_pointer, $parent_data_pointer, $schema, 'maxProperties', [
    'min' => $schema->maxProperties,
    'count' => $count,
]));

Judging from the pattern in the rest of min/max validation code, I believe the first keyword argument must be named 'max':

    'max' => $schema->maxProperties,

Should be good for detailed error reporting, and other ValidationError-to-string conversion.

Regards,
Kody 🐎

Return all errors for allOf conditions

Can I get all errors when allOf conditions are defined?
Right now if first condition fails second is not checked at all.

What if we change the code in file opis/json-schema/src/Validator.php (line 861 to 907) to

// allOf
if (property_exists($schema, 'allOf')) {
    if (!is_array($schema->allOf)) {
        throw new SchemaKeywordException(
            $schema,
            'allOf',
            $schema->allOf,
            "'allOf' keyword must be an array, " . gettype($schema->allOf) . " given"
        );
    }
    if (count($schema->allOf) === 0) {
        throw new SchemaKeywordException(
            $schema,
            'allOf',
            $schema->allOf,
            "'allOf' keyword must not be empty"
        );
    }
    $newbag = $bag->createByDiff();
    $errors = []; // $errors = null;
    $valid = true;

    foreach ($schema->allOf as &$one) {
        if (!is_bool($one) && !is_object($one)) {
            throw new SchemaKeywordException(
                $schema,
                'allOf',
                $schema->allOf,
                "'allOf' keyword items must be booleans or objects, found " . gettype($one)
            );
        }
        if (!$this->validateSchema($document_data, $data, $data_pointer, $parent_data_pointer, $document, $one, $newbag)) {
            $valid = false;
            $errors = array_merge($errors, $newbag->getErrors()); // $errors = $newbag->getErrors();
            // break;
        }
        $newbag->clear();
    }
    unset($one, $newbag);
    if (!$valid) {
        $ok = false;
        $bag->addError(new ValidationError($data, $data_pointer, $parent_data_pointer, $schema, 'allOf', [], $errors));
        if ($bag->isFull()) {
            return false;
        }
    }
    unset($errors);
}

Support new draft 2019-09

I wanted to use the most recent JSON Schema draft (2019-09), but it isn’t supported by Opis JSON Schema yet.

I’ll gladly use draft-07 in the meantime, but I imagine the project will need to support it at some point, so here you go.

$data = json_decode( $data_text, true ) fails to validate

Hi guys

I don't know if this is a bug or not, but as soon as you use
$data = json_decode( $data_text, true );
to decode JSON the validator will fail on a simple JSON/Schema:

[
	{
		"rule":
		{
			"ip": "*",			
			"mac": "*"			
		},
		"host": "http://iptv.nevronitv.si",
		"rsync":
		{
			"images": "images",	
			"videosd": "videosd"
		}
	}
]

{
	"type": "array",
	"minItems": 1,
	"items":
	{
		"type": "object",
		"properties":
		{

			"host":
			{
				"type": "string"
			}
		},
		"required": ["host"]
	}
}

with 'host' is required error.

Now to me class or associative arrays are both completely valid to decode JSON, so maybe it's a bug.

BrW

contentEncoding support for image/jpeg

I am trying to validate a base64 encoded JPEG with the following:

"picture": {
	"description": "photo with a resolution of 150x200 pixels",
	"type": "string",
	"contentMediaType": "image/jpeg",
	"contentEncoding": "base64",
	"maxLength": 32768
},

My jpeg image starts with data:image/jpeg;base64, followed by the base64 data.
However, I get the following validation error:

  keyword: contentEncoding
  keywordArgs:
  - encoding: base64

If I remove the "contentEncoding": "base64", from my schema.json file, the validation works.
If I remove the data:image/jpeg;base64, header of the picture field, I get an error about contentMediaType.

Is base64 encoding supported for JPEG images? Am I doing something wrong?

Cannot validate draft 7 schema against to draft 7 schema

Simple example:

$data = json_decode(file_get_contents('tests/official/drafts/draft7.json'), false);
$v = new Validator();
$v = new Opis\JsonSchema\Validator();
$result = $v->dataValidation($data, $data, -1);

var_dump($result->isValid())
// bool(false)

Error Schema must be an object or a boolean, NULL given in

Hi, I have this script for validation
index.php

<?php
use Opis\JsonSchema\{Validator, ValidationResult, ValidationError, Schema};
require 'vendor/autoload.php';
$loader = new \Opis\JsonSchema\Loaders\File("http://test/", [
    "",
]);
$data = json_decode('{
  "title": "TEST",
  "code": "TWOR"
}');
$validator = new Validator();
$validator->setLoader($loader);
/** @var ValidationResult $result */
$result = $validator->uriValidation($data, "http://test/cTest.json");
if ($result->isValid()) {
    echo '$data is valid', PHP_EOL;
} else {
    /** @var ValidationError $error */
    var_dump($result->getErrors());
    $error = $result->getFirstError();
    echo '$data is invalid', PHP_EOL;
    echo "Error: ", $error->keyword(), PHP_EOL; 
    echo json_encode($error->keywordArgs(), JSON_PRETTY_PRINT), PHP_EOL;
}

this JSON schema cTest.json

{
	"$schema": "http://json-schema.org/draft-07/schema#",
	"type": "object",
	"properties": { 
		"title": {
			"$ref": "http://test/title.json#"
		},
		"code": {
			"$ref": "http://test/title.json#"
		}
	},
	"required": ["title", "code"],
    "additionalProperties": false
} 

and this title.json

{
  	"$id": "title.json",
  	"type": "string",
	"minLength": 2,
	"pattern": "^(.*)$"
}

These 3 files are in the same folder.
Script shows this error:
Fatal error: Uncaught Opis\JsonSchema\Exception\InvalidSchemaException: Schema must be an object or a boolean, NULL given in /Data/Projects/test/vendor/opis/json-schema/src/Schema.php:63
Stack trace:
#0 /Data/Projects/test/vendor/opis/json-schema/src/Loaders/File.php(63): Opis\JsonSchema\Schema->__construct(NULL, 'http://test/titl...')
#1 /Data/Projects/test/vendor/opis/json-schema/src/Validator.php(419): Opis\JsonSchema\Loaders\File->loadSchema('http://test/titl...')
#2 /Data/Projects/test/vendor/opis/json-schema/src/Validator.php(322): Opis\JsonSchema\Validator->validateRef(Object(stdClass), 'TEST workcenter', Array, Array, Object(Opis\JsonSchema\Schema), NULL, Object(Opis\JsonSchema\ValidationResult))
#3 /Data/Projects/test/vendor/opis/json-schema/src/Validator.php(1908): Opis\JsonSchema\Validator->validateSchema(Object(stdClass), 'TEST workcenter', Array, Array, Object(Opis\JsonSchema\Schema), Object(stdClass), Object(Opis\JsonSchema\ValidationResult))
#4 /Data/Projects/test/vendor/opis/json-schema/src/Validator.php(946): Opi in /Data/Projects/test/vendor/opis/json-schema/src/Schema.php on line 63

What am I doing wrong?
Thank you.

FileLoader breaks on empty prefix

Example:

$loader = new \Opis\JsonSchema\Loaders\File('', ['./']);
$validator = new Validator(null, $loader);
$result = $validator->schemaValidation($data, $schema);

expected behavior: $result->isValid() is either true or false

actual behavior:

Fatal error: Uncaught Opis\JsonSchema\Exception\SchemaNotFoundException: Schema '/tester.json' was not found or could not be loaded in ../vendor/opis/json-schema/src/Validator.php on line 416

Reason:
src/Loaders/File.php:54
strpos($uri, $this->prefix) breaks, if $this->prefix is empty

Bonus: The documentation is slightly incorrect, the constructor has to be called with an array as second parameter.
https://docs.opis.io/json-schema/1.x/php-loader.html -> "File loader"

$loader = new \Opis\JsonSchema\Loaders\File(
"http://example.com/",
"/path/to/schemas"
);

$result->isValid() returns true when required node is missing [BUG]

Hello,

I'm using the following code (basically is the Quick start code):

use Opis\JsonSchema\{
    Validator, ValidationResult, ValidationError, Schema
};

$data = '
{
    "header": {
        "id": "72d61e51-c742-4788-89b9-155c229d8f97",
        "timestamp": "2019-07-24T09:47:18.9654339Z",
        "topic": "TBC",
        "originator": "Stock Publisher",
        "schemaVersion": "0.1"
    },
    "body": {
        "stock": [{
            "sku": "12345678",
            "sites": [{
                "siteId": "001",
                "merretStock": "10",
                "ofsAssigned": "2",
                "buffer": "????",
                "availableStock": "8"
                }, {
                "siteId": "002",
                    "merretStock": "10",
                    "ofsAssigned": "2",
                    "buffer": "????",
                    "availableStock": "8"
            }],
            "adjustments": "10"
        }]
    }
}
';
$data = json_decode($data);
$schema = Schema::fromJsonString(file_get_contents('resources/Api/schema/stock.schema.json'));

$validator = new Validator();

/** @var ValidationResult $result */
$result = $validator->schemaValidation($data, $schema);

if ($result->isValid()) {
    echo '$data is valid', PHP_EOL;
} else {
    /** @var ValidationError $error */
    $error = $result->getFirstError();
    echo '$data is invalid', PHP_EOL;
    echo "Error: ", $error->keyword(), PHP_EOL;
    echo json_encode($error->keywordArgs(), JSON_PRETTY_PRINT), PHP_EOL;
}

For schema, I'm using this file -> https://pastebin.com/raw/6WpwbUaq

If I'm going to remove/rename the sites node, I'm getting:

$data is invalid
Error: required
{
    "missing": "sites"
}

If I'm going to remove/rename the sku node (same applies for adjustments node), I'm getting:

$data is valid

I guess that's a bug unless I'm missing something.

Having issues getting the $map feature to work

I'm trying to follow https://docs.opis.io/json-schema/1.x/mappers.html yet I keep getting errors.

validate.php is the code I'm using. I can get our-user and standard-user to validate. Yet when trying to validate standard-user.json to our-user.schema, or vise-versa I receive the following error:

Array
(
[0] => Opis\JsonSchema\ValidationError Object
(
[data:protected] => stdClass Object
(
[name] => John
[birthday] => 1970-01-01
)

        [dataPointer:protected] => Array
            (
            )

        [schema:protected] => stdClass Object
            (
                [$id] => /our-user.json#
                [type] => object
                [properties] => stdClass Object
                    (
                        [firstName] => stdClass Object
                            (
                                [type] => string
                            )

                        [lastName] => stdClass Object
                            (
                                [type] => string
                            )

                        [email] => stdClass Object
                            (
                                [type] => string
                                [format] => email
                            )

                    )

                [required] => Array
                    (
                        [0] => firstName
                        [1] => lastName
                        [2] => email
                    )

                [additionalProperties] =>
                [allOf] => Array
                    (
                        [0] => stdClass Object
                            (
                                [$ref] => standard-user.json
                                [$map] => stdClass Object
                                    (
                                        [name] => stdClass Object
                                            (
                                                [$ref] => /firstName
                                            )

                                        [birthday] => 1970-01-01
                                    )

                                [$_base_id] => /our-user.json#
                                [$_path] => Array
                                    (
                                        [0] => allOf
                                        [1] => 0
                                    )

                            )

                    )

                [$schema] => http://json-schema.org/draft-07/schema#
                [$_base_id] => /our-user.json#
                [$_path] => Array
                    (
                    )

            )

        [keyword:protected] => required
        [keywordArgs:protected] => Array
            (
                [missing] => firstName
            )

        [subErrors:protected] => Array
            (
            )

    )

)

Any help would be appreciated.

validate.zip

Looking at the future json-schema drafts

Well, I have to say that I'm somehow disappointed. draft-08's focus is schema reuse
and I don't see any improvement. Here are some proposals, which will "help" reusability

Ok, now lets take it one by one, but first I'll define the reused schema:

{
    "$id": "user",
    "type": "object",
    "properties": {
        "name": {"type": "string"}
    },
    "required": ["name"],
    "additionalProperties": false
}

$merge & $patch

Extending it with $merge

{
    "$id": "extended-user",
    "$merge": {
        "source": {"$ref": "user"},
        "with": {
            "properties": {
                "age": {"type": "integer"}
            },
            "required": ["age"]
        }
    }
}

The result is

{
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer"}
    },
    "required": ["age"],
    "additionalProperties": false
}

Yes, the name is no longer required because was overwritten by extended-user's $merge.
So it doesn't work how you expect. Wait! I could use as an allOf and put there the required.
And if the user also have an allOf? You guessed, the same problem, so it won't work.

Don't dispare, it's not all lost. Here comes $patch:

{
    "$id": "extended-user",
    "$patch": {
        "source": {"$ref": "user"},
        "with": [
            {"op": "add", "path": "/properties/age", "value": {
                "type": "integer"
            }},
            {"op": "add", "path": "/required", "value": "age"}
        ]
    }
}

Now, isn't that readable?

If you still think that it is readable, try adding three more properties to extended-user.
For a better taste just use a pinch of remove operation.
Now just do the same in other schemas and after two months you'll loose your time
trying to understand what that schema really does.
Probably you'll end up moving the properties from user to extended-user just because it is easier to read and debug.

$spread

Extending it with $spread

{
    "$id": "extended-user",
    "properties": {
        "age": {"type": "integer"},
        "$spread": [
            {"$ref": "user"}
        ]
    },
    "required": ["age"]
}

The result is

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

Ok, in order to also make name required you have to manually add it.
Also you have to manually add additionalProperties. But what if user also have an allOf?

Yes, you guessed, it won't work.

unevaluatedProperties

This one is a little bit trickier. You can use it in conjunction with allOf, anyOf, oneOf.
The ideea is that unevaluatedProperties knows what properties from ***Of were checked.

Extending it with unevaluatedProperties

{
    "$id": "extended-user",
    "properties": {
        "age": {"type": "integer"}
    },
    "required": ["age"],
    "allOf": [
        {"$ref": "user"}
    ],
    "unevaluatedProperties": false
}

Well, it will not work because user has additionalProperties set to false.

Anyway, the working design for unevaluatedProperties is not in our case.
It cannot be used for extending, only for ignoring this or that but in a limited way, because it will produce unexpected behaviour eventually.

Here it is an example of usage (we don't care about user schema for now)

{
    "$id": "hip-hop",
    "properties": {
        "common": {"type": "string"}
    },
    "required": ["common"],
    "anyOf": [
        {
            "properties": {
                "foo": {"type": "integer"}
            },
            "required": ["foo"]
        },
        {
            "properties": {
                "bar": {"type": "string"},
                "baz": {"type": "number"}
            },
            "required": ["bar"]
        }
    ],
    "unevaluatedProperties": false
}

Examples of data

1 - valid (common & first item of anyOf)

{
    "common": "this is common and required",
    "foo": 1
}

2 - valid (common & second item of anyOf)

{
    "common": "this is common and required",
    "bar": "bar value",
    "baz": 1
}

3 - valid (common & second item of anyOf, baz is not required)

{
    "common": "this is common and required",
    "bar": "bar value"
}

4 - invalid (baz is not present in first item of anyOf and bar is not present in the second one)

{
    "common": "this is common and required",
    "foo": 1,
    "baz": 1
}

5 - ???

{
    "common": "this is common and required",
    "foo": 1,
    "bar": "bar value",
    "baz": 1
}

Well, in theory this should be valid. unevaluatedProperties must know what properties were checked.
But, if an implementation of json-schema decides to do some optimizations, this will not work anymore. In case of anyOf once you've found a valid schema it will not make sense to check remaining schemas.
So, if json-schema doesn't allow optimizations by design it means that apps using it will spend most of the time checking things that don't make sense checking.
And if this is the case, you better start typing if-else and forget about json-schema.

Epilogue

Maybe you saw, but we already added a new keyword $map (besides $filters and $vars)
for opis/json-schema to allow a simple extending.

$map solves the following problem:

Given a schema and an object, map the properties of the object to match the schema properties.

So, if the schema is:

{
    "$id": "example",
    "type": "object",
    "properties": {
        "foo": {"type": "string"},
        "bar": {"type": "number"}
    }
}

and the current object is

{
    "a": "value for foo",
    "b": 123
}

you can validate it using $ref and $map

{
    "$ref": "example",
    "$map": {
        "foo": {"$ref": "0/a"},
        "bar": {"$ref": "0/b"}
    }
}

Please note that inside $map (and $vars) the $ref property is a (relative) json pointer for
current object.

Here is how we can extend the user

{
    "$id": "extended-user",
    "type": "object",
    "properties": {
        "age": {"type": "integer"}
    },
    "required": ["age"],
    "allOf": [
        {
            "$ref": "user",
            "$map": {
                "name": {"$ref": "0/name"}
            }
        }
    ]
}

You can even have other property for name in your extended schema

{
    "$id": "extended-user",
    "type": "object",
    "properties": {
        "full-name": {"type": "string"},
        "age": {"type": "integer"}
    },
    "required": ["full-name", "age"],
    "allOf": [
        {
            "$ref": "user",
            "$map": {
                "name": {"$ref": "0/full-name"}
            }
        }
    ]
}

I know that this example is too simple, and it doesn't make sense to just validate again
if name is a string since you already checked full-name, but what if user schema
also contains an allOf? What if the constraints for name are more complex?

Here is a more complex example (using two base schemas for our extended-user schema)

{
    "$id": "user",
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "active": {"type": "boolean"},
        "required": ["name", "active"]
    },
    "allOf": [
        ... other checks for user
    ],
    "additionalProperties": false
}
{
    "$id": "user-permissions",
    "type": "object",
    "properties": {
        "realm": {
            "type": "string"
        },
        "permissions": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "enabled": {"type": "boolean"}
                },
                "required": ["name", "enabled"],
                "additionalProperties": false,
                "allOf": [
                    ... other checks for permission
                ]
            }
        }
    },
    "required": ["realm", "permissions"],
    "additionalProperties": false
}

Our extended user

{
    "$id": "extended-user",
    "type": "object",
    "properties": {
        "first-name": {"type": "string"},
        "last-name": {"type": "string"},
        "is-admin": {"type": "boolean"},
        "admin-permissions": {
            "type": "array",
            "items": {
                "enum": ["create", "read", "update", "delete"]
            }
        },
    },
    "required": ["first-name", "last-name", "is-admin", "admin-permissions"],
    "additionalProperties": false,
    "allOf": [
        {
            "$ref": "user",
            "$map": {
                "name": {"$ref": "0/last-name"},
                "active": true
            }
        },
        {
            "$ref": "user-permissions",
            "$map": {
                "realm": "administration",
                "permissions": {
                    "$ref": "0/admin-permissions",
                    "$each": {
                        "name": {"$ref": "0"},
                        "enabled": {"$ref": "2/is-admin"}
                    }
                }
            }
        }
    ]
}

So if the data for extended-user schema is

{
    "first-name": "Json-Schema",
    "last-name": "Opis",
    "is-admin": true,
    "admin-permissions": ["create", "delete"]
}

the mapped data provided to user schema (first item of allOf) will be

{
    "name": "Opis",
    "active": true
}

and the mapped data provided to user-permissions schema (second item of allOf) will be

{
    "realm": "administration",
    "permissions": [
        {
            "name": "create",
            "enabled": true
        },
        {
            "name": "delete",
            "enabled": true
        }
    ]
}

As you can see, with $map you can add only what properties you want, you can handle nested properties, you can provide default values, and you can even use $each to map arrays.

The advantage is that I can change extended-user schema however I want without touching user and user-permissions schemas.

And for validation this is verbose, clear and flexible.

Anyway, another method you can use to simplify your schemas is using $ref together with $vars.

Here is an example where $vars is handy

{
    "$id": "settings-type",
    "definitions": {
        "type-A": {
            ...
        },
        "type-B": {
            ...
        },
        ...
    }
}
{
    "type": "object",
    "properties": {
        "type": {"enum": ["A", "B", ...]},
        "settings": {
            "$ref": "settings-type#/definitions/type-{typeName}",
            "$vars": {
                "typeName": {"$ref", "1/type"}
            }
        }
    }
    "required": ["type", "settings"]
}

Without $vars you'll probably need an anyOf or oneOf which will be very slow.
But in this way you can add as many definitions as you want to settings-type schema,
or even better (and recommended), you can use different schema files for each type
and load only needed schemas, because it doesn't make sanse to load and check things that
will never change the final result.

Unexpected InvalidJsonPointerException when no errors limit set and required property is missing

I wouldn't expect an exception Opis...\InvalidJsonPointerException, when a missing path/property is required. For me, required properties should have a higher "priority" and references should be checked only after requirements of a higher "order" are meet.

If I take your example and change the $data variable (remove "password") and set no errors limit (-1) I get such exception:

use Opis\JsonSchema\{
    Validator,
    ValidationResult,
    ValidationError,
    FilterContainer,
    IFilter
};

$filters = new FilterContainer();

$filters->add("string", "sameAs", new class implements IFilter {
    /**
     * @inheritDoc
     */
    public function validate($data, array $args): bool {
        return isset($args['value']) && $data === $args['value'];
    }
});

$validator = new Validator();
$validator->setFilters($filters);

$schema = <<<'JSON'
{
    "type": "object",
    "properties": {
        "password": {
            "type": "string"
        },
        "passwordRepeat": {
            "type": "string",
            "$filters": {
                "$func": "sameAs",
                "$vars": {
                    "value": {
                        "$ref": "1/password"
                    }
                }
            }
        }
    },
    "required": ["password", "passwordRepeat"]
}
JSON;

$schema = json_decode($schema, false);

$data = (object) [
    // "password" => "secret",
    "passwordRepeat" => "secret"
];

/** @var ValidationResult $result */
$result = $validator->dataValidation($data, $schema, -1 /* no limit of errors /*);
if ($result->isValid()) {
    echo "Valid", PHP_EOL;
} else {
    echo "Invalid", PHP_EOL;
}

I would expect a validation error (ValidationError) instead. In my opinion, an exception would be appropriate only when "password" is not required (and missing of course).

Problem with validation when an empty object is in an array.

I have the following simplified JSON (in reality is has a few more fields):

{
 "options":[{}]
}

This is the JSON Schema:

{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "type": "object",
    "properties": {
        "options": {
            "type": "array",
          	"items": {
                "type": "object",
          }
        },
    },
    "required": ["options"]
}

This is the code that I use to validate:

        $decodedContent = json_decode(json_encode($content), false);
        Log::info('Content ' . var_export($decodedContent, true));

        $schema = Schema::fromJsonString(file_get_contents('......'));

        $validator = new Validator();
        
        $result = $validator->schemaValidation($decodedContent, $schema);

        if ($result->isValid()) {
            return response()->json([
                'message' => 'Ok!' . $appId . " " . $messageId], 200);
        } else {
            /** @var ValidationError $error */
            $error = $result->getFirstError();
            Log::info('$data is invalid');
            Log::info("Error: " . $error->keyword());
            Log::info(json_encode($error->keywordArgs(), JSON_PRETTY_PRINT));
            return response()->json([], 500);
        }

The output that I get is this one:

[2019-09-12 19:02:20] local.INFO: Content (object) array(
   'options' => 
  array (
    0 => 
    array (
    ),
  ),
...
)  
[2019-09-12 19:02:20] local.INFO: $data is invalid  
[2019-09-12 19:02:20] local.INFO: Error: type  
[2019-09-12 19:02:20] local.INFO: {
    "expected": "object",
    "used": "array"
}  

The JSON should be valid but the result says it is not. I think this is caused by the associative arrays. Is there a way to work around this?

Thank you for the work on this library!

Regex Patterns are not quoted

<?php

require_once __DIR__ . '/../vendor/autoload.php';

$schema = <<<'JSON'
    {
      "$schema": "https://json-schema.org/draft-07/schema#",
      "type": "object",
      "properties": {
            "url": {
              "type": "string",
              "format": "regex",
              "pattern": "^https?:\/\/.+$"
            }
        }
    }
JSON;

$validator = new \Opis\JsonSchema\Validator();

$result = $validator->schemaValidation(
    (object) ['url' => 'https://example.com'],
    \Opis\JsonSchema\Schema::fromJsonString($schema)
);

assert($result->isValid());

/**
 * Expected: no warning, no output
 *
 * Actual: PHP Warning:  preg_match(): Unknown modifier '/' in vendor/opis/json-schema/src/Formats/Regex.php on line 34
 *         PHP Warning:  assert(): assert($result->isValid()) failed in ...
 */

A fix and PR will follow today.

PropertyNames and pattern

Hello,

I have seen that there is a problem to validate string numbers in PropertyNames with pattern. I use https://regex101.com to check the patterns. For example:

"propertyNames": { "pattern": "^[a-z]{3}$" }

Here I want 3 letters keys in lower case .
"aaa" -> OK
"aaaa" -> FAIL
"ab" > FAIL
"123"-> OK ¿?¿
"123456789" -> OK ¿?

If I use a string with only numbers it works when it should fail (https://regex101.com/r/GnsYSL/1)

"propertyNames": { "pattern": "^(0?[1-9]|1[0-6])$" },

Numbers from 0 to 16 (01, 02 etc are valid too)
"aaa" -> FAIL
"aaaa" -> FAIL
"ab" > FAIL
"10" -> OK
"123"-> OK ¿?¿
"123456789" -> OK ¿?

All numbers are OK when it shouldn't (https://regex101.com/r/GnsYSL/2)

To work as I want, I have to change propertyNames like this:
"propertyNames": { "type": "integer", "minimum": 1, "maximum": 16 },

But it is not so obvious, because in JSON the keys are always strings.

regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.