jsonschema icon indicating copy to clipboard operation
jsonschema copied to clipboard

Incorrect validation of valid payload with valid schema

Open DoDoENT opened this issue 5 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