Skip to content

Fix json_schema() dropping constraints for a Schema nested in a Schema - #354

Open
gaoflow wants to merge 1 commit into
keleshev:masterfrom
gaoflow:fix-json-schema-nested-const
Open

Fix json_schema() dropping constraints for a Schema nested in a Schema#354
gaoflow wants to merge 1 commit into
keleshev:masterfrom
gaoflow:fix-json-schema-nested-const

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 29, 2026

Copy link
Copy Markdown

The bug

json_schema() renders a Schema nested directly inside another Schema as an empty {} schema, which matches anything — silently dropping the constraint and contradicting what validate() enforces.

from schema import Schema, Const

Schema(Const("fixed")).json_schema("http://x")
# -> {'$id': 'http://x', '$schema': '...draft-07...'}      # no "const"! accepts anything

Schema(Const("fixed")).validate("other")
# -> raises SchemaError (correctly rejects)

So the generated JSON schema disagrees with the validator on a representable construct. The same happens for any directly-nested schema, e.g. Schema(Schema(int)).json_schema(...){} instead of {"type": "integer"}.

Const("fixed").json_schema(...) and Schema("fixed").json_schema(...) both work — the loss only happens when the Schema/Const is wrapped in another Schema.

Root cause

In Schema.json_schema, _priority() classifies a Schema instance as VALIDATOR, and the only VALIDATOR branch in the generator handles Regex:

elif flavor == VALIDATOR and type(s) == Regex:
    ...
else:
    if flavor != DICT:
        return return_schema    # empty {}  <-- nested Schema lands here

validate() unwraps a nested schema (s = s.schema), but json_schema() never recurses into it, so every non-Regex nested Schema falls through to the no-op default.

Fix

Recurse into a nested Schema and merge its generated schema instead of dropping it:

if isinstance(s, Schema):
    return_schema.update(
        _json_schema(s, is_main_schema=False, allow_reference=allow_reference)
    )
elif flavor == TYPE:
    ...

Or/And/Regex are not Schema subclasses, so their existing branches are unaffected; only nested Schema instances (Const, Schema(Schema(...)), …) now expand.

Tests / verification

Added test_json_schema_const_and_nested_schema covering Const(literal)const, Const(type)type, Schema(Schema(int))type, and Const as a dict value. The new test fails on master and passes with the fix. Full suite incl. the README doctests is green (121 passed); ruff/ruff-format clean; no new mypy findings.

json_schema() rendered a Schema nested directly inside another Schema --
e.g. Const, or Schema(Schema(...)) -- as an empty {} schema, which matches
anything and so contradicts what validate() enforces. _priority() classifies
a Schema instance as VALIDATOR, and the only VALIDATOR branch handled Regex,
so any other nested Schema fell through to the no-op default and was dropped.

Recurse into the nested Schema and merge its generated schema, so
Const("x") -> {"const": "x"}, Const(int) -> {"type": "integer"} and
Schema(Schema(int)) -> {"type": "integer"}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant