keleshev / schema

Schema validation just got Pythonic
MIT License
2.86k stars 214 forks source link

Let additional properties be subschema #281

Closed cuyt closed 3 months ago

cuyt commented 2 years ago

Proposal

When converting a Schema to JSON schema expand the additionalProperties field into a bona fide sub-schema where appropriate instead of evaluating just to true.

Use Case

I'm using Schema to validate JSON encoded configurations. I'd like to use both programmatic checks as well as dynamic schema validation functionalities of the IDE (VSCode) using JSON schemas.

When validating with the validate() method of a Schema object validation can be precise for 'generic' properties of a given type, e.g. str. However, when using the to_json() conversion to generate JSON schema for use in the IDE environment, validation for non-named (generic) properties is limited.

Consider the following Schema and a JSON object to validate:

s = Schema({str: str})
example_json = {
  "a_string" : "this",
  "an_integer" : 5
}

Validating with s.validate(example_json) yields a SchemaUnexpectedTypeError: 5 should be instance of 'str'.

However, jsonschema.validate(example_json, s.json_schema('schema-id')) (or any JSON schema implementation validating the exported JSON schema) validates this without throwing an exception. This is because the json_schema call converts the Schema into the following JSON schema:

{
    "type": "object",
    "properties": {},
    "required": [],
    "additionalProperties": true,
    "$id": "generic-schema",
    "$schema": "http://json-schema.org/draft-07/schema#"
}

which evaluates all additional properties to true.

Change

This PR proposes to ,where applicable, iteratively expand the additionalProperties element into a sub_schema, instead of just to true, in order to allow stricter validation on 'non-named' (generic) properties. Only str and object keys will result in sub-schema expansion, in line with the original rules for evaluating additionalProperties to True.

With the proposed change, the above Schema would evaluate instead to the following JSON schema:

{   
    "type": "object",
    "properties": {},
    "required": [],
    "additionalProperties": {"type": "string"},
    "$id": "generic-schema",
    "$schema": "http://json-schema.org/draft-07/schema#"
}

Note the appearance of the sub-schema in the "additionalProperties" field.

See the additional unit tests for further details.