jsonschema icon indicating copy to clipboard operation
jsonschema copied to clipboard

Incorrect validation of valid payload with valid schema

Open DoDoENT opened this issue 7 months ago • 2 comments

I have a schema that heavily utilises $dynamicRef to inject parts of schema from other files.

Namely, here is the base schema:

base.json:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "base.json",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "orderNumber": {
            "type": "integer",
            "minimum": 0
        },
        "downstreamNumber": {
            "$dynamicRef": "#specialDownstreamNumber"
        },
        "schemaName": {
            "$dynamicRef": "#concreteName"
        },
        "schemaVersion": {
            "$dynamicRef": "#concreteVersion"
        },
        "timestamp": {
            "type": "integer"
        },
        "data": {
            "$dynamicRef": "#concreteData"
        }
    },
    "required": [
        "data",
        "orderNumber",
        "schemaName",
        "schemaVersion",
        "downstreamNumber",
        "timestamp"
    ],
    "$defs": {
        "name": {
            "const": "base",
            "$dynamicAnchor": "concreteName"
        },
        "version": {
            "const": "0.0.0",
            "$dynamicAnchor": "concreteVersion"
        },
        "data": {
            "not": true,
            "$dynamicAnchor": "concreteData"
        },
        "downstreamNumber": {
            "type": "integer",
            "minimum": 1,
            "$dynamicAnchor": "specialDownstreamNumber"
        }
    }
}

And here is the specific JSON that fills in the data for the base:

camera.json:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "camera.json",
    "$ref": "base.json",
    "$defs": {
        "name": {
            "const": "camera.info",
            "$dynamicAnchor": "concreteName"
        },
        "version": {
            "const": "1.0.0",
            "$dynamicAnchor": "concreteVersion"
        },
        "data": {
            "$dynamicAnchor": "concreteData",
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "cameraFacing": {
                    "enum": [
                        "Front",
                        "Back"
                    ]
                },
                "cameraFrameSize": {
                    "$ref": "#/$defs/size"
                },
                "roi": {
                    "$ref": "#/$defs/size"
                },
                "viewPortAspectRatio": {
                    "type": "number"
                }
            },
            "required": [
                "cameraFacing",
                "cameraFrameSize",
                "roi",
                "viewPortAspectRatio"
            ]
        },
        "downstreamNumber": {
            "type": "integer",
            "minimum": 0,
            "$dynamicAnchor": "specialDownstreamNumber"
        },
        "size": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "width": {
                    "type": "integer"
                },
                "height": {
                    "type": "integer"
                }
            },
            "required": [
                "width",
                "height"
            ]
        }
    }
}

Here is the JSON that I'm trying to validate:

{
    "orderNumber": 4,
    "downstreamNumber": 1,
    "schemaVersion": "1.0.0",
    "schemaName": "camera.info",
    "timestamp": 1756525700,
    "data": {
        "cameraFacing": "Back",
        "cameraFrameSize": {
            "width": 3841,
            "height": 2161
        },
        "roi": {
            "width": 3840,
            "height": 1858
        },
        "viewPortAspectRatio": 2.066
    }
}

Here is how I'm doing the validation:

import json

from pathlib import Path

from jsonschema.validators import Draft202012Validator
from referencing import Registry, Resource


ROOT = Path(__file__).absolute().parent
SCHEMAS = ROOT / Path("schema")


def validate_file(validator: Draft202012Validator, file: Path):
    with open(file, "rt", encoding="utf-8") as f:
        instance = json.load(f)

    instance = json.loads(file.read_text())

    print(f"Validating {file}...")
    validator.validate(instance)


def retrieve_from_filesystem(uri: str):
    path = SCHEMAS / Path(uri)
    contents = json.loads(path.read_text())
    return Resource.from_contents(contents)


def main():
    registry = Registry(retrieve=retrieve_from_filesystem)

    schema_file = SCHEMAS / Path("camera.json")
    with open(schema_file, "rt", encoding="utf-8") as f:
        schema = json.load(f)

    validator = Draft202012Validator(
        schema,
        registry=registry
    )

    tests_path = ROOT / Path("payload")
    for file in tests_path.iterdir():
        validate_file(validator, file)


if __name__ == "__main__":
    main()

The validation fails with exception:

jsonschema.exceptions._WrappedReferencingError: PointerToNowhere: '/$defs/size' does not exist within {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'base.json', 'type': 'object', 'additionalProperties': False, 'properties': {'orderNumber': {'type': 'integer', 'minimum': 0}, 'downstreamNumber': {'$dynamicRef': '#specialDownstreamNumber'}, 'schemaName': {'$dynamicRef': '#concreteName'}, 'schemaVersion': {'$dynamicRef': '#concreteVersion'}, 'timestamp': {'type': 'integer'}, 'data': {'$dynamicRef': '#concreteData'}}, 'required': ['data', 'orderNumber', 'schemaName', 'schemaVersion', 'downstreamNumber', 'timestamp'], '$defs': {'name': {'const': 'base', '$dynamicAnchor': 'concreteName'}, 'version': {'const': '0.0.0', '$dynamicAnchor': 'concreteVersion'}, 'data': {'not': True, '$dynamicAnchor': 'concreteData'}, 'downstreamNumber': {'type': 'integer', 'minimum': 1, '$dynamicAnchor': 'specialDownstreamNumber'}}}

Which is incorrect, as camera.json does contain /$defs/size. I've tried validating the payload with a different validator and it validates the payload successfully.

I don't see anything wrong with my schema definitions and boon has no problem validating the payload.

Is this a bug in jsonschema library or did I do something wrong and boon fails to detect it?

DoDoENT avatar May 29 '25 15:05 DoDoENT

Hi there! It'd help a bit (and make it more likely I can look at this) if you minimize your example to be the smallest possible schema + instance which exhibits the issue (by removing things irrelevant to the error and doing things like moving your schema to be inline in the Python file rather than in multiple files).

Anything's possible, though both this library and boon pass all of the test suite so if there's an issue our first step will be to minimize it so we can add a test to the suite.

Julian avatar May 29 '25 15:05 Julian

Here is the smallest repro I managed to make:

base.json:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "base.json",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "data": {
            "$dynamicRef": "#concreteData"
        }
    },
    "required": [
        "data"
    ],
    "$defs": {
        "data": {
            "not": true,
            "$dynamicAnchor": "concreteData"
        }
    }
}

camera.json:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "camera.json",
    "$ref": "base.json",
    "$defs": {
        "data": {
            "$dynamicAnchor": "concreteData",
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "cameraFrameSize": {
                    "$ref": "#/$defs/size"
                }
            },
            "required": [
                "cameraFrameSize"
            ]
        },
        "size": {
            "type": "integer"
        }
    }
}

payload.json:

{
    "data": {
        "cameraFrameSize": 1920
    }
}

The exception is now:

jsonschema.exceptions._WrappedReferencingError: PointerToNowhere: '/$defs/size' does not exist within {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'base.json', 'type': 'object', 'additionalProperties': False, 'properties': {'data': {'$dynamicRef': '#concreteData'}}, 'required': ['data'], '$defs': {'data': {'not': True, '$dynamicAnchor': 'concreteData'}}}

Boon successfully validates the payload.

DoDoENT avatar May 29 '25 16:05 DoDoENT