JSON Schema Validation validate array items not working - json

Here is the thing I'm trying to validate:
[REMOVED AFTER EDIT - PLEASE SEE EDIT BELOW FOR UP TO DATE CODE]
This should fail because only the item that should be acceptable here is "merchants" - but the validation doesn't fail. It passes.
I've can't work this out. I've tried a few approaches and it's got me nowhere.
What is wrong here?
---------------------- EDIT ----------------------------
I've been asked to provide more code. Here is the payload I need to validate. It's in PHP.
$payload = (object) [];
$payload->query_string = (object) [];
$payload->query_string->include = (object) [
"merchant_channel",
"merchant",
];
Here is the line that will run the validator:
$this->validator->validate(
$payload,
['$ref' => 'file://Schemas/the-json-file.json']
);
And here is the file that I'm running the validator against:
{
"type": "object",
"properties": {
"query_string": {
"type": "object",
"properties": {
"include": {
"type": "object",
"properties": {
"values": {
"type": "array",
"items": {
"allOf": [
{
"type": "string",
"enum": [
"language"
]
}
]
}
}
}
}
}
}
}
}
I'm using this package:
https://github.com/justinrainbow/json-schema
This package is quite simply....
A PHP Implementation for validating JSON Structures against a given Schema.
You shouldn't have to know PHP to able to answer my original question which is specific to JSON Schema Validation!
Please don't comment/answer saying something like "You've missed a quote" or "You've missed a closing bracket". Obviously I've had to cut this code down so I can post it on Stack Overflow.

It lacks starting and ending curly braces.

Related

Can't validate a json with a schema using an external schema file

My issue
I need to validate a json file from a Json Schema.
I have a main schema and this schema should load an external schema file to validate some part of the json.
For different reasons I can't merge them. I do need 2 schema file.
I am using a $ref statement as proposed in this doc:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/test-json?view=powershell-7.2&WT.mc_id=DT-MVP-5004831
but whatever I try I have this error:
Cannot parse the JSON schema.
What I need
What is the right schema syntax to do this in my case.
Test case
I have this directories on my disk:
.
test.ps1
----/jsons
params.json
----/schemas
securities.json
storages.json
I run validation from this pretty simple PowerShell (test.ps1):
# read json param
$testFile = Get-Content "./jsons/params.json" -Encoding UTF8 | convertfrom-json -Depth 50
$testee = $testFile.parameters | convertto-json
# read json schema
$schemasFile = (Get-ChildItem -Path "./schemas/storages.json").FullName
# test json from schema
$result = $testee | Test-Json -SchemaFile $schemasFile
$result
My json (params.json):
{
"parameters": {
"storages": [
{
"comment": "Very important storage",
"name": "fdlmsto",
"securities": [
{
"comment": "Critical rule",
"kind": "MSI"
}
]
}
]
}
}
The main schema (storages.json):
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": [
"storages"
],
"properties": {
"storages": {
"$id": "#/properties/storages",
"type": "array",
"additionalProperties": false,
"items": [
{
"$id": "#/properties/storages/items",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"comment"
],
"properties": {
"$ref": "#/schemas/securities.json",
"name": {
"type": "string",
"$id": "#/properties/storages/items/properties/name"
},
"comment": {
"type": "string",
"$id": "#/properties/storages/items/properties/comment"
}
}
}
]
}
}
}
and the child schema (securities.json):
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"additionalProperties": false,
"required": [
"securities"
],
"properties": {
"securities": {
"$id": "#/properties/securities",
"type": "array",
"additionalProperties": false,
"items": {
"$id": "/properties/securities/items",
"anyOf": [
{
"$id": "#/properties/securities/items/anyOf/0",
"type": "object",
"additionalItems": false,
"additionalProperties": false,
"required": [
"kind",
"comment"
],
"properties": {
"comment": {
"type": "string",
"$id": "#/properties/securities/items/anyOf/0/properties/comment"
},
"kind": {
"type": "string",
"$id": "#/properties/securities/items/anyOf/0/properties/kind"
}
}
}
]
}
}
}
}
What I tested
I tested several syntaxe for $ref:
#/schemas/securities.json
./schemas/securities.json
/schemas/securities.json
....
I also tried to set the schema in the repository of test.ps1
I checked that this is the securities section that don't work
Thank you
I edit the question to leave what worked for me:
Change storages.json like this:
"properties": {
"securities": {
"$ref": "securities1.json#/securities"
},
"name": {
"type": "string"
},
"comment": {
"type": "string"
}
}
Change securities.json as:
{
"securities": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"comment"
],
"properties": {
"comment": {
"type": "string"
},
"kind": {
"type": "string"
}
}
}
}
}
And of course I applied all changed suggested in the post
It appears that the powershell cmdlet is powered by NJsonSchema, which at this stage doesn't support JSON Schema draft 2020-12.
There are several issues I see.
storages.json
"$schema": "https://json-schema.org/draft/2020-12/schema" declares this is a draft 2020-12 schema. As your schema doesn't use any 2020-12 features, I'd suggest using draft 7 as it'll definitely be supported by NJsonSchema.
Your schema is declaring itself to be the meta-schema with "$id": "https://json-schema.org/draft/2020-12/schema". The meta-schema is a special schema that validates other schemas. What you probably want here is just storages.json, or even better, a full URI like https://examp.le/schemas/storages.json. It's just an identifier though; it doesn't need to be downloadable.
Remove the internal $ids. They're only specifying relative location and not providing any more detail. This kind of thing is generally calculated at runtime.
Your $ref is wrong. #/schemas/securities.json is going to try to search for this location inside the current schema. You need a full URI here, and you'll want to declare that same URI as $id in securities.json as well. This may be what is producing the error.
At #/properties/storages, you have "type": "array" and "additionalProperties": false. Maybe you meant additionalItems? additionalProperties only works on objects. (I also see this in securities.json at #/properties/securities.)
Your use of items is an array. This will only validate the first item. I think you're wanting to validate all the items. If so, just use the schema as the value for items; don't wrap it in an array. (also seen in securities.json)
securities.json
This schema uses draft 4, which is quite old and a bit incompatible with later drafts. Specifically, I see it's using $id in several places, which replaced draft 4's id in draft 6. Since draft 4 doesn't recognize $id, it'll probably ignore them. Moreover, $ref-ing between drafts isn't something that's required (or perhaps even addressed) by the specification. (I added some tests to the test suite around this only about a week ago.) As with storages.json I'd suggest using draft 7 here.
As mentioned above, you'll want to add an $id to the root so that the $ref in storages.json can identify this schema properly.
As with storages.json, remove the interal $ids; they're not providing any additional function.
You use an anyOf but only declare a single schema. You don't need this. Just move the keywords in that subschema up to where the allOf is.
{
"type": "object",
"allOf": [ { ... } ]
}
// becomes
{
"type": "object",
...
}
Sorry this sounds like a pile-on. I realize you might be new with JSON Schema. The learning curve can be pretty steep.
Instead of the documentation provided by MS, I'd use Understanding JSON Schema for how to write schemas. Use the MS docs only for how to use the cmdlet.
Just to add one point to the excellent answer above: the array form of items is not valid in 2020-12 anyway - it was replaced with prefixItems.
But the advice to use draft-07 throughout is the right advice, as is the advice to use the schema form of items anyway.

Documentation based on JSON Schema

I'd like to use my JSON schemas to generate the documentation.
In the example below, I want to list all combinations ErrorNumber/ErrorMessage available in my output messages in JSON.
But I can't find a way on the object level, my attempts with "examples" or "enum" failed.
Does anyone have a solution?
{
"type": "object",
"required": [
"ErrorNumber",
"ErrorMessage"
],
"properties": {
"ErrorNumber": {
"$id": "#root/ErrorNumber",
"type": "integer"
},
"ErrorMessage": {
"$id": "#root/ErrorMessage",
"type": "string"
}
}
}
Did you mean to write "$ref" where you use "$id" in the example?
Where exactly did you have problems with enum? The following works fine for me with a draft-2020-12 Validator (and after removing your "$id"!):
{
// ... your JSON here ...
"enum": [
{"ErrorNumber": 200, "ErrorMessage": "OK"},
{"ErrorNumber": 404, "ErrorMessage": "Not found."}
// ...
]
}
Different approaches, in case you still can change that:
If your error numbers start at 0 and are contiguous, then an Array of messages might serve your purpose.
Alternatively an object with numerical keys might:
{
"200": "OK",
"404": "Not found."
}

Conditionally determine the required-ness of a field

I need to refer to a sub-schema of certain property (Kind in the example) from a different property in the schema, and then enforce some more conditions on it. Important thing to note is I cannot make those changes where I've defined Kind, I need to refer to it from some other property and then add conditionals on top of it.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"Kind": {
"$id": "#/properties/Kind",
"type": "string",
"enum": [
"Foo",
"Bar"
]
}
},
"allOf": [
{
"if": {
"$ref": "#/properties/Kind",
"const": "Foo"
},
"then": {
"required": [
"MyField"
]
}
}
]
}
A json object like below should fail the validation, because MyField property is absent
{
"Kind": "Foo"
}
I don't want the following solution, since this is just a simplified version and ultimately I want to refer to Kind value from another property. If I do following, then #/properties/Kind is interpreted relative to where I refer Kind so it doesn't refer to the Kind at the top level. I want a solution which uses the $ref and $id keywords.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"Kind": {
"$id": "#/properties/Kind",
"type": "string",
"enum": [
"Foo",
"Bar"
]
}
},
"allOf": [
{
"if": {
"properties": {"Kind":{
"const":"Foo"
}}
},
"then": {
"required": [
"MyField"
]
}
}
]
}
To summarize, let's say I've following JSON structure. The last allOf statement is what I need to add.
- Kind ( enum of One,Two)
- Other
- MyField
- ConditionField
- allOf ( which enforces the required-ness of MyField based on ConditionField)
- allOf ( MyField should be not-required if Kind is One)
[ To add this last conditional, I need to reference the value of Kind.
I'm hoping providing $id to Kind and referring to it with $ref should be my approach,
which doesn't seem to be working]
To summarize even further, I would get my answer if we're able to get the first snippet work using $id and $ref.
There seem to be some misunderstandings that are making it difficult to fully understand the problem here, but one part of the edited question makes enough sense that I think I can start things off and we can iterate on the answer as necessary.
Let's start with some of the things that don't make sense in hopes that it helps clarify possible misunderstandings.
$ref can't change the behavior of a schema. If you can't do something without $ref, then you can't make the schema behave another way by introducing $ref. The only exception to that rule is recursive schemas, which would require an infinitely large and repeating schema without using $ref.
I'm not sure what you are trying to get from $id, but it's pretty safe to say you don't need it for this. In any case, the $id used the question is invalid. An anchor can not have a / in it. Even if it was valid, it would be redundant because you can reference that location with the same JSON Pointer without an anchor.
MyField should be not-required if Kind is One
I'm not sure if "not-required" means forbidden or optional. Everything is optional by default in JSON Schema, so if you meant optional, there is nothing to do here. Therefore, I'll assume for now that you mean forbidden. Here's what that would look like.
{
"type": "object",
"properties": {
"Kind": { "enum": ["One", "Two"] },
"Other": {
"type": "object",
"properties": {
"MyField": {}
}
}
},
"allOf": [
{
"if": {
"properties": {
"Kind": { "const": "One" }
},
"required": ["Kind"]
},
"then": {
"properties": {
"Other": {
"not": { "required": ["MyField"] }
}
}
}
}
]
}

What is the correct use of json-schema used with ajv?

Good morning,
I have a problem using ajv with a json-schema, when using a $ref to something defined in the same json file. I suspect that the problem is with the use of ids, I would understand more about that.
My files are:
definitions.json
{
"$schema":"http://json-schema.org/draft-06/schema#",
"definitions": {
"person":{
"$schema":"http://json-schema.org/draft-06/schema#",
"$id": "http://asite.org/schemas/person.json",
"type":"object",
"properties": {
"name":{"type":"string"},
"surname":{"type":"string"},
"email": {"type": "string", "format": "email"},
"sex":{"$ref": "types.json#/definitions/gender"}},
"required":["name", "surname", "sex", "email"]
},
"member":{
"$schema":"http://json-schema.org/draft-06/schema#",
"type": "object",
"$id": "http://asite.org/schemas/member.json",
"allOf":[
{"$ref": "#/definitions/person"},
{
"properties": {
"id":{"type":"integer"},
"role":{"$ref": "types.json#/properties/man_role"},
"is_expert":{"type":"boolean", "default":false},
"is_board":{"type":"boolean", "default": false}
}
}
]
}
},
"type":"object",
"properties": {
"person": {"$ref": "#/definitions/person"},
"member": {"$ref": "#/definitions/member"}
}
}
types.json
{
"$schema":"http://json-schema.org/draft-06/schema#",
"$id": "http://asite.org/schemas/types.json",
"type": "object",
"definitions": {
"gender":{"enum": ["male", "female"]},
"man_role":{"enum": ["admin", "supervisor", "clerk", "none"]}
},
"properties":{
"gender":{"$ref": "#/definitions/gender"},
"man_role": {"$ref": "#/definitions/man_role"}
}
}
I looked at this question, but I didn't figure it out how to correct my example.
The error I get is:
MissingRefError: can't resolve reference #/definitions/person from id http://asite.org/schemas/member.json
The references work if I try with VisualStudio Code, e.g I can create a "member" and it recognizes also the properties of "person".
Can anyone tell me how should write these schemas to make them work with ajv?
The code that generates the error is:
import Ajv, {JSONSchemaType, DefinedError} from "ajv"
import {Person, Member} from "./definitions";
import addFormats from "ajv-formats"
const ajv = new Ajv();
addFormats(ajv);
ajv.addMetaSchema(require("../node_modules/ajv/lib/refs/json-schema-draft-06.json"))
const types = require("../types.json");
const PersonSchema : JSONSchemaType<Person> = require('../definitions.json').definitions.person;
const MemberSchema: JSONSchemaType<Member> = require('../definitions.json').definitions.member;
ajv.addSchema(types);
const isPerson = ajv.compile(PersonSchema);
const isMember = ajv.compile(MemberSchema)
//other stuff, use of isPerson and isMember on example objects
Your intuition is correct, the $ref isn't resolving as you expect because of the $id. $refs are resolved against the $id of the schema. In this case, { "$ref": "#/definitions/person" } is resolved against "$id": "http://asite.org/schemas/member.json" resulting in http://asite.org/schemas/member.json#/definitions/person which doesn't exist. The solution is to use the $id of the person schema rather than a relative path: { "$ref": "person.json" }.
As an aside, you might prefer the alternate API for compiling schemas that works better when dealing with bundled schemas like your "definitions.json". You can load the whole file and then compile the individual schemas you want by their $id.
const ajv = new Ajv();
ajv.addMetaSchema(draft06);
ajv.addSchema(definitions);
ajv.addSchema(types);
const isPerson = ajv.getSchema("http://asite.org/schemas/person.json");
const isMember = ajv.getSchema("http://asite.org/schemas/member.json");

Json schema relative $ref to current "id"

I have searched SO and JSON schema documentation for a few days now, but I'm struggling to get my JSON Schema references to work across multiple files in the same directory.
My root schema would be an oneOf object:
{
"$schema": "http://json-schema.org/draft-04/schema",
"id": "http://localhost/json-editor/schema/function.json",
"title": "Function",
"oneOf": [
{
"$ref": "./fn_md5.json"
},
{
"$ref": "fn_sha1.json"
}
]
}
Now after reading the docs and most questions here, I'd assume both ./fn_md5.json and fn_sha1.json would be resolved to http://localhost/json-editor/schema/fn_....json (where the ... represents their respective name, of course). However, the resolver keeps looking into http://localhost/json-editor/fn_....json, which appears to be relative to the application's url (JDorn's JSON Editor running at my http://localhost/json-editor/).
Am I understanding the schema and id wrong, or could it be an error with the editor's URL resolver?
Thanks!
for find the relative references you need use $id like this:
{
"$schema": "http://json-schema.org/draft-04/schema",
"id": "http://localhost/json-editor/schema/function.json",
"$id":"https://www.baseurl.com/"
"title": "Function",
"oneOf": [
{
"$id": "./fn_md5.json"
},
{
"$id": "fn_sha1.json"
}
]
}
source: https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-01#section-8.2.4