Cannot reference definitions within a nested, named-schema definition scope
Description
Implementation requires a single, global reference for all definitions. Making reference to a "Complex Structure" example provided by JSONSchema.org.
The schema leverages the idea that a definition using an $id signals a new scope for that definition and references are only made to other definitions within that scope. Specifically, the root's $defs block only contains an address definition while that address block itself has a definitions block:
{
"$defs": {
"address": {
"$id": "/schema/address",
"definitions": {
"state": {}
}
}
}
}
In this example, the root object has references to the address definition's '$id' value using "/state/address". Within the address block, it also contains a definition (state) which is referenced from properties as "#/definitions/state".
In this case, the # symbol relates to the scope within the address block.
Full Complex JSON Schema Example
{
"$id": "https://example.com/schemas/customer",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"shipping_address": { "$ref": "/schemas/address" },
"billing_address": { "$ref": "/schemas/address" }
},
"required": ["first_name", "last_name", "shipping_address", "billing_address"],
"$defs": {
"address": {
"$id": "/schemas/address",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "$ref": "#/definitions/state" }
},
"required": ["street_address", "city", "state"],
"definitions": {
"state": { "enum": ["CA", "NY", "... etc ..."] }
}
}
}
}
NOTE: Have verified this schema works with the
jsonschemaPython library using a generated JSON object using this project.
Expected
Should be able to run a jsf.JSF(schema) with this JSON Schema:
import json
import jsf
schema = json.load(open("complex.schema.json" , "r"))
gen = jsf.JSF(schema)
new_json = gen.generate()
print(json.dumps(new_json, indent=2))
Actual
Running the above code generates AttributeError: 'NoneType' object has no attribute 'name' on parser.py#L181.
Making adjustments to the schema definition to flatten the dependency tree and removing references looking at internal dependencies by their $id, we can make it work.
Adjusted JSON Schema Example
{
"$id": "https://example.com/schemas/customer",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"shipping_address": { "$ref": "#/$defs/address" },
"billing_address": { "$ref": "#/$defs/address" }
},
"required": ["first_name", "last_name", "shipping_address", "billing_address"],
"$defs": {
"address": {
"$id": "/schemas/address",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "$ref": "#/$defs/state" }
},
"required": ["street_address", "city", "state"]
},
"state": { "enum": ["CA", "NY", "... etc ..."] }
}
}
This is related but not a duplicate of #84. That issue specifically relates to the order of operations involving applying depedencies before all dependencies are captured.
How would this reference find anything? "$ref": "/schemas/address" as there is no schemas key defined in your JSON schema.
If you look at the document on bundling, it describes the base schema provides the Base URI for the document. Unless a sub-schema or reference defines their own domain or static reference, it will resolve the remainder of the URI against the Base URI for it's identity.
When
$idis used in a subschema, it indicates an embedded schema. The identifier for the embedded schema is the value of$idresolved against the Base URI of the schema it appears in.
So in the example, the Base URI is https://example.com/schemas/customer. Within the sub-schema, the address schema's $id value is /schemas/address. This requires the domain which it inherits by resolving against the Base URI. Same for the $ref value within the root schema and in the sub-schema
Once parsed, this results in fully formed domain identities for each schema.
{
"$id": "https://example.com/schemas/customer",
...
"properties": {
"shipping_address": { "$ref": "https://example.com/schemas/address" },
},
"$defs": {
"address": {
"$id": "https://example.com/schemas/address",
...
"properties": {
"state": { "$ref": "https://example.com/schemas/address#/definitions/state" }
},
"definitions": {
"state": { "enum": ["CA", "NY", "... etc ..."] }
}
}
}
}