Json Schema Properties with $ref - json

Trying to use $ref for the entirety of properties. I can't tell what this is syntax valid but doesn't validate the payload. This should fail but doesn't.
I've also tried "$ref": "file:./ref.json".
schema:
{
"animal": {
"properties":{
"allOf": {"$ref": "file:./ref.json"}
}
},
"required": ["animal"]
}
ref.json:
{
"action":{
"type": "string"
},
"required": ["action"]
}
payload
{
"animal": {
"action": 2
}
}

"allOf": {"$ref": "file:./ref.json"} is not syntactically valid -- the value of an allOf must be an array. (your evaluator should be giving you a warning about this.)
JSON Schema evaluators are not required to support loading external files from disk or the network. Check your documentation for how to add documents to the evaluator so they can be used by $ref. (your evaluator should be giving you a warning when you reference an unknown resource.)
The reason why you are not seeing the above errors is because your overall schema has no recognized keywords in it -- you are missing a "properties": { ... } wrapped around the entire schema. The top level "keyword" is "animal", which is not recognized, therefore there are no recognized keywords anywhere in the schema, therefore there is nothing to make it return an invalid result.

Related

When using the combining schema as the parent schema: Could not resolve schema reference '#foo'. Path 'not.allOf[0]'

Consider an example in
https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/master/tests/draft6/ref.json#L414
the original schema is:
{
"allOf": [{
"$ref": "http://localhost:1234/bar#foo"
}],
"definitions": {
"A": {
"$id": "http://localhost:1234/bar#foo",
"type": "integer"
}
}
}
which is valid.
For this schema S, if I want to create a new schema by adding a combining schema outside this schema, like: {"not":S}, which is:
{
"not": {
"allOf": [{
"$ref": "http://localhost:1234/bar#foo"
}],
"definitions": {
"A": {
"$id": "http://localhost:1234/bar#foo",
"type": "integer"
}
}
}
}
with the Location-independent identifier with absolute URI, also use $id. But it is invalid.
The error message(using https://www.jsonschemavalidator.net/):
Error parsing schema
Message:
Error when resolving schema reference 'http://localhost:1234/bar#foo'. Path 'not.allOf[0]', line 3, position 20.
A similar example is:
{
"not": {
"allOf": [{
"$ref": "#foo"
}],
"definitions": {
"A": {
"$id": "#foo",
"type": "integer"
}
}
}
}
with the Location-independent identifier, is also invalid.
I cannot figure out why it is invalid after adding a parent schema if there are $ref and $id at the same time...
Here {"not":S} is just one possibility, also can consider {"anyOf":[{S}]}.
I know that $id declares a base URI against which $ref URI-references are resolved.
But what is the problem with the above schemas?
And how should I correct them?
I will so appreciate it if someone helps me out...
You are correct here, and your schema is correct. I would call this a bug in the implementation.
Your schema is valid for JSON Schema draft-07, but not JSON Schema draft 2019-09 or above. For 2019-09 and above, your $id value can't contain a non-empty fragment.
You can see the correct and valid behaviour on another web based validator, a web based version of the HyperJump validator: https://json-schema.hyperjump.io
This web based implementation defaults to latest (2020-12), but you can specify a draft version using $schema (for example "$schema": "http://json-schema.org/draft-07/schema#").
To see this working on HyperJump, either modify your schema to remove fragments in the identifier URIs, or specify that you're using draft-07.

How should a json-schema (draft7) implementation resolve `$ref`s defined in unknown keywords?

JSON-Schema-Test-Suite defines schemas such as this, and I assume they are valid:
{
"tilda~field": {"type": "integer"},
"slash/field": {"type": "integer"},
"percent%field": {"type": "integer"},
"properties": {
"tilda": {"$ref": "#/tilda~0field"},
"slash": {"$ref": "#/slash~1field"},
"percent": {"$ref": "#/percent%25field"}
}
}
Take this example below:
{
"$id": "http://example.com/root.json",
"definitions": {
"A": { "type": "integer" }
},
"properties": {
"$id": {
"type": "string"
},
"attributes": {
"$ref": "#/tilda~0field/slash~1field/$id"
}
},
"tilda~field": {
"$id": "t/inner.json",
"slash/field": {
"$id": {
"$id": "test/b",
"$ref": "document.json"
}
}
}
}
Which of the following is the $ref at #/tilda~0field/slash~1field/$id/$ref resolved to?
http://example.com/root.json/document.json
http://example.com/t/document.json
http://example.com/t/test/document.json
Which of the $ids in #/tilda~0field must be considered as baseURI for the $ref in question and why.
$refs are not resolved within unknown keywords because $ref and $id only apply inside schemas. Let's look at an example to see what I mean by this.
{
"$id": "http://example.com/foo",
"type": "object",
"properties": {
"aaa": {
"const": { "$ref": "#/definitions/bbb" }
},
"bbb": { "$ref": "#/definitions/bbb" }
},
"ccc": { "$ref": "#/definitions/bbb" },
"definitions": {
"bbb": { "type": "string" }
}
}
The document as a whole is a schema, so /$id is understood by JSON Schema.
The properties keyword is defined as an object whose values are schemas. Therefore, the value at /properties/bbb is a schema and /properties/bbb/$ref is understood by JSON Schema.
The value of the const keyword is unconstrained. The value at /properties/aaa/const may look like a schema, but it's just a plain JSON object. Therefore /properties/aaa/const/$ref is not understood by JSON Schema.
The value at /ccc is not a JSON Schema keyword, so it's value is not constrained and not a schema. Therefore, the $id and $ref keywords are not understood by JSON Schema.
That's how it works now. When you go back to older drafts (draft-05 iirc), it's a little different. Before then, $ref was defined in a separate specification called JSON Reference. JSON Schema extended JSON Reference. Therefore, the semantics of $ref applied everywhere it appeared in a JSON Schema.
EDIT:
What happens when a $ref inside a known keyword references a schema deep inside an unknown keyword. for example, what if #/properties/bbb referenced #/ccc?
That's a really good question. Referencing #/ccc should be an error because #/ccc is not a schema and $ref only allows you to reference a schema.
I just saw your question on the JSON Schema site in an issue. I'll post here what I posted there as well. However, also look at the edit.
Which of the following is the $ref at #/tilda~0field/slash~1field/$id/$ref resolved to?
I think Section 8.2 gives the answer: "A subschema's "$id" is resolved against the base URI of its parent schema." This means that it will be http://example.com/t/test/document.json.
Which of the $ids in #/tilda~0field must be considered as baseURI for the $ref in question and why.
The base URI for this is the $id at the root, re-routed by the $id in tilda~field.
Start with http://example.com/root.json.
Change folders to t and use file inner.json.
Change folders to test (inside t) and use file document.json.
EDIT
Having a look at #customcommander's reply and realizing that the value in tilde~field isn't processed as a schema, I'd like to say that the #ref wouldn't be processed at all. It's just a plain JSON string with no inherent meaning.
Are you sure your second schema is valid though?
For example according to the JSON Schema Core specification, the $id property should be a string
If present, the value for this keyword MUST be a string
Therefore the following looks wrong to me:
"slash/field": {
"$id": {
"$id": "test/b",
"$ref": "document.json"
}
}
Then I think that your first $ref isn't correct either:
"attributes": {
"$ref": "#/tilda~0field/slash~1field/$id"
}
It should probably read:
"attributes": {
"$ref": "#/tilda~0field/slash~1field"
}
The same specification document also says this about $id and $ref:
Cf this example
The "$id" keyword defines a URI for the schema, and the base URI that other URI references within the schema are resolved against.
Or as Ajv simply puts it:
$ref is resolved as the uri-reference using schema $id as the base URI […].
Given the current state of your schema, I can only approximate the answer to your question but the ref would probably resolve to something like t/inner.json#document.json

Automatic `$schema` support in JSON schema?

I want to describe this JSON:
{
"key1": {},
"key2": {}
}
so I created this JSON schema:
{
"type": "object",
"patternProperties": {
".+": {
"type": "object"
}
}
}
The problem is that when I add a $schema link to the JSON, it's not valid:
First, it seems strange that $schema would need any kind of special treatment but even if I try this:
{
"type": "object",
"properties": {
"$schema": {
"type": "string"
}
},
"patternProperties": {
".+": {
"type": "object"
}
}
}
it's not fixed:
I'm browsing a couple of schemas at http://schemastore.org/json/ and they don't seem to have any special treatment of $schema. How does it work?
The $schema keyword is used to declare that a JSON fragment is actually a piece of JSON Schema.
But it's not used in your JSON when it's not a schema, i.e. it is not used in your JSON data.
You then use a validator to match the schema against the JSON data. For exmple you can use this validator. In the left you specify the schema in the right you specify JSON data (without any reference or link to the schema, you don't use the $schema keyword in the right side)
The $schema keyword specifies which version of the JSON Schema standard the schema applies to (again the JSON schema, not the JSON data). Most of the time is:
"$schema": "http://json-schema.org/draft-04/schema#"
More info here
The accepted answer is correct, but here is the workaround you need.
{
"type": "object",
"properties": {
"$schema": {
"type": "string"
}
},
"additionalProperties": {
"type": "object"
}
}
additionalProperites applies only to properties that are defined in properties. patternProperties, on the other hand, applies to any property that it matches. The way you wrote it with patternProperties means that "$schema" must be a string and it must be an object. Since those two things can never both be true, "$schema" will never validate with any value.

Enforcing "style" rules in JSON Schema files?

I am looking at using JSON Schemas for an upcoming project, and looking for a way to validate our naming conventions/style and consistency rules in the JSON Schema file. Somewhat similar to StyleCop or Checkstyle.
Using this samples from JSON Schema Lint to illustrate:
{
"description": "Any validation failures are shown in the right-hand Messages pane.",
"type": "object",
"properties": {
"foo": {
"type": "number"
},
"bar": {
"type": "string",
"enum": [
"a",
"b",
"c"
]
}
}
}
Imagine another developer wants to add a new property, but I want to prevent property names from being upper-case (baz instead of Baz) or maybe boolean properties should start with "is" (isBaz). Is there a way to "unit test" the JSON Schema file and check for that?
"Baz": {
"type": "boolean"
},
It feels like a custom validator for the JSON Schema file (vs. using the JSON Schema to validate the JSON output). Does something like that already exist, or do I just parse the JSON schema file myself and write the rules?
It's completely possible to write a meta-schema that enforces this constraint on your schemas. Let's construct it step-by-step:
1. Constraining property names
The key part is to use patternProperties to specify which property names are allowed, and additionalProperties to disallow anything else:
{
"patternProperties": {
"^[a-z]+([A-Z][a-z]*)*$": {}
},
"additionalProperties": false
}
(For this example, I've used the regex ^[a-z]+([A-Z][a-z]*)*$ to detect alphabetic-only lowerCamelCase)
Note that it doesn't matter whether provide any constraints for suitably-named properties (here it's just the empty schema {}). However, the presence of this definition means that any matching property is allowed, while anything else is banned by additionalProperties.
Fancier constraints
For other constraints (such as your "boolean properties must start with is" one), you just add more complex entries here.
This answer focuses more on how to make a generic recursive naming-style schema. It's already pretty long, so if you're looking for guidance on how to express a specific constraint, then it might be neater to ask as a separate question.
2. Applying to the properties property
This bit's pretty simple - make these constraints apply to the appropriate part of the schema:
{
"properties": {
"properties": {"$ref": "#/definitions/propertyStyleRule"}
},
"definitions": {
"propertyStyleRule": {
"patternProperties": {
"^[a-z]+([A-Z][a-z]*)*$": {}
},
"additionalProperties": false
}
}
}
3. Make it recursive
In fact, you don't just want to cover sub-schemas inside "properties", but also "items", "anyOf", etc.
Here it gets quite long, so I'll omit most of it, but basically you go through every keyword that might contain a schema, and make sure they are subject to the same naming-scheme by referencing the root schema:
{
"properties": {
"properties": {"$ref": "#/definitions/propertyStyleRule"},
"additionalProperties": {"$ref": "#"},
"items": {"$ref": "#"},
"not": {"$ref": "#"},
"allOf": {"$ref": "#"},
...
},
"definitions": {
"propertyStyleRule": {
"patternProperties": {
"^[a-z]+([A-Z][a-z]*)*$": {"$ref": "#"}
},
"additionalProperties": false
}
}
}
Note: we've also now replaced the empty schema ({}) in our "propertyStyleRule" definition with a reference back to the root ({"$ref": "#"}), so the sub-schemas inside properties also recurse properly.
4. Hang on, some of those keywords can be arrays, or booleans, or...
OK, so there's an obvious problem here: "not" holds a schema, so that's fine, but "allOf" holds an array of schemas, "items" can hold either, and "additionalProperties" can be a boolean.
We could do some fancy switching with different types, or we could simply add an items entry to our root schema:
{
"items": {"$ref": "#"},
"properties": {
...
},
"definitions": {
"propertyStyleRule": {...}
}
}
Because we haven't specified a type, our root schema actually allows instances to be objects/arrays/boolean/string/whatever - and if the instance isn't an object, then properties is just ignored.
Similarly, items is ignored unless the instance is an array - but if it is an array, then the entries must also follow the root schema. So it doesn't matter whether the value of "items" is a schema or an array of schemas, it recurses properly either way.
5. Schema maps
For a few keywords (like "patternProperties" or "definitions") the value is not a schema, it's a map of strings to schemas, so you can't just reference the root schema. For these, we'll make a definition "schemaMap", and reference that instead:
{
"items": {"$ref": "#"},
"properties": {
"properties": {"$ref": "#/definitions/propertyStyleRule"},
"additionalProperties": {"$ref": "#"},
"items": {"$ref": "#"},
"not": {"$ref": "#"},
"allOf": {"$ref": "#"},
...
"patternProperties": {"$ref": "#/definitions/schemaMap"},
...
},
"definitions": {
"schemaMap": {
"type": "object",
"additionalProperties": {"$ref": "#"}
},
"propertyStyleRule": {...}
}
}
... and you're done!
I've left out details, but hopefully it's clear enough how to write the full version.
Also, once you've written this once, it should be pretty easy to adapt it for different style rules, or even applying similar constraints to the names in "definitions", etc. If you do write a schema like this, please consider posting it somewhere so that other people can adapt it! :)

JSON Schema regarding use of $ref

I understand that $ref takes a URI to a json schema to use but where does $ref : "#" point to?
Does it just mean use the current schema for this block level? Or does it mean to use the root level schema defined in the root level id?
Thanks
EDIT:
So if I have:
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
}
Because it lacks an id field it will attempt to validate the instance items with the root schema first and then if that fails try to validate it with the schemaArray schema defined in the definitions schema, right?
So if I change it to:
"items": {
"id" : "#/items",
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
}
Then the first subschema in anyOf array will point to the items schema itself?
EDIT #2: Okay so if I had:
"items": {
"id" : "itemSchema",
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
}
and
"stringArray": {
"type": "array",
"items": { "$ref" : "itemSchema" },
"minItems": 1,
"uniqueItems": true
}
"stringArray"'s "items" field would be validated against the above "itemsSchema"?
Also does the second $ref in 'anyOf' work by going to the root and then traversing down the path till it hits that schema?
Thanks!
OK: each $ref is resolved into a full URI. Once that is done, all your questions are answered by asking the question: What schema would I end up with, if I simply fetched that URI? Where the $ref is, how it was loaded, all of that is irrelevant - it's entirely dependent on the resolved URI.
The library might take some shortcuts (like caching documents so they are only fetched once, or trusting one schema to "speak for" another), but those are all implementation details.
Response to original question:
# is not special: all values of $ref are resolved as URIs relative to the current document (or the closest value of "id", if there is one).
Therefore, if you haven't used "id", then # will point to the root of the schema document. If you fetched your schema from http://example.com/schema, then a {"$ref": "#"} anywhere inside that will resolve to http://example.com/schema#, which is the document itself.
It is different when you use "id", because it changes the "base" schema against which the $ref is resolved:
{
"type": "array",
"items": {
"id": "http://example.com/item-schema",
"type": "object",
"additionalProperties": {"$ref": "#"}
}
}
In that example, the $ref resolves to http://example.com/item-schema#. Now, if your JSON Schema setup trusts the schema it already has, then it can re-use the value from "items".
However, the point is there is nothing special about # - it just resolves to a URI like any other.
Response to EDIT 1:
Your first example is correct.
However, your second is unfortunately not. This is because of the way that fragments resolution works for URIs: one fragment completely replaces another. When you resolve the # against the "id" value of #/items, you don't end up with #/items again - you end up with #. So in your second example, the first entry in "anyOf" will still resolve to the root of the document, just as in the first example.
Response to EDIT 2:
Assuming the document is loaded from http://example.com/my-schema, the full URIs of your two $refs are:
http://example.com/itemSchema#
http://example.com/itemSchema#/definitions/schemaArray
For the first one, the library may use the schema it already has, but it might not - after all, looking at the URIs, http://example.com/my-schema might not be trusted to accurately represent http://example.com/itemSchema.
For the second one - that's not going to work, because the "itemSchema" doesn't have a "definitions" section, so that $ref won't resolve properly at all.