jsf icon indicating copy to clipboard operation
jsf copied to clipboard

Cannot reference definitions within a nested, named-schema definition scope

Open wsidl opened this issue 2 years ago • 3 comments

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 jsonschema Python 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 ..."] }
  }
}

wsidl avatar Dec 12 '23 01:12 wsidl

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.

wsidl avatar Dec 12 '23 02:12 wsidl

How would this reference find anything? "$ref": "/schemas/address" as there is no schemas key defined in your JSON schema.

ghandic avatar Jan 29 '24 22:01 ghandic

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 $id is used in a subschema, it indicates an embedded schema. The identifier for the embedded schema is the value of $id resolved 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 ..."] }
      }
    }
  }
}

wsidl avatar Jan 30 '24 05:01 wsidl