| ========================== | |
| Handling Validation Errors | |
| ========================== | |
| .. currentmodule:: jsonschema.exceptions | |
| When an invalid instance is encountered, a `ValidationError` will be | |
| raised or returned, depending on which method or function is used. | |
| .. autoexception:: ValidationError | |
| :noindex: | |
| The information carried by an error roughly breaks down into: | |
| =============== ================= ======================== | |
| What Happened Why Did It Happen What Was Being Validated | |
| =============== ================= ======================== | |
| `message` `context` `instance` | |
| `cause` `json_path` | |
| `path` | |
| `schema` | |
| `schema_path` | |
| `validator` | |
| `validator_value` | |
| =============== ================= ======================== | |
| .. attribute:: message | |
| A human readable message explaining the error. | |
| .. attribute:: validator | |
| The name of the failed `keyword | |
| <https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural>`_. | |
| .. attribute:: validator_value | |
| The associated value for the failed keyword in the schema. | |
| .. attribute:: schema | |
| The full schema that this error came from. This is potentially a | |
| subschema from within the schema that was passed in originally, | |
| or even an entirely different schema if a :kw:`$ref` was | |
| followed. | |
| .. attribute:: relative_schema_path | |
| A `collections.deque` containing the path to the failed keyword | |
| within the schema. | |
| .. attribute:: absolute_schema_path | |
| A `collections.deque` containing the path to the failed | |
| keyword within the schema, but always relative to the | |
| *original* schema as opposed to any subschema (i.e. the one | |
| originally passed into a validator class, *not* `schema`\). | |
| .. attribute:: schema_path | |
| Same as `relative_schema_path`. | |
| .. attribute:: relative_path | |
| A `collections.deque` containing the path to the | |
| offending element within the instance. The deque can be empty if | |
| the error happened at the root of the instance. | |
| .. attribute:: absolute_path | |
| A `collections.deque` containing the path to the | |
| offending element within the instance. The absolute path | |
| is always relative to the *original* instance that was | |
| validated (i.e. the one passed into a validation method, *not* | |
| `instance`\). The deque can be empty if the error happened | |
| at the root of the instance. | |
| .. attribute:: json_path | |
| A `JSON path <https://goessner.net/articles/JsonPath/index.html>`_ | |
| to the offending element within the instance. | |
| .. attribute:: path | |
| Same as `relative_path`. | |
| .. attribute:: instance | |
| The instance that was being validated. This will differ from | |
| the instance originally passed into ``validate`` if the | |
| validator object was in the process of validating a (possibly | |
| nested) element within the top-level instance. The path within | |
| the top-level instance (i.e. `ValidationError.path`) could | |
| be used to find this object, but it is provided for convenience. | |
| .. attribute:: context | |
| If the error was caused by errors in subschemas, the list of errors | |
| from the subschemas will be available on this property. The | |
| `schema_path` and `path` of these errors will be relative | |
| to the parent error. | |
| .. attribute:: cause | |
| If the error was caused by a *non*-validation error, the | |
| exception object will be here. Currently this is only used | |
| for the exception raised by a failed format checker in | |
| `jsonschema.FormatChecker.check`. | |
| .. attribute:: parent | |
| A validation error which this error is the `context` of. | |
| ``None`` if there wasn't one. | |
| In case an invalid schema itself is encountered, a `SchemaError` is | |
| raised. | |
| .. autoexception:: SchemaError | |
| :noindex: | |
| The same attributes are present as for `ValidationError`\s. | |
| These attributes can be clarified with a short example: | |
| .. testcode:: | |
| schema = { | |
| "items": { | |
| "anyOf": [ | |
| {"type": "string", "maxLength": 2}, | |
| {"type": "integer", "minimum": 5} | |
| ] | |
| } | |
| } | |
| instance = [{}, 3, "foo"] | |
| v = Draft202012Validator(schema) | |
| errors = sorted(v.iter_errors(instance), key=lambda e: e.path) | |
| The error messages in this situation are not very helpful on their own. | |
| .. testcode:: | |
| for error in errors: | |
| print(error.message) | |
| outputs: | |
| .. testoutput:: | |
| {} is not valid under any of the given schemas | |
| 3 is not valid under any of the given schemas | |
| 'foo' is not valid under any of the given schemas | |
| If we look at `ValidationError.path` on each of the errors, we can find | |
| out which elements in the instance correspond to each of the errors. In | |
| this example, `ValidationError.path` will have only one element, which | |
| will be the index in our list. | |
| .. testcode:: | |
| for error in errors: | |
| print(list(error.path)) | |
| .. testoutput:: | |
| [0] | |
| [1] | |
| [2] | |
| Since our schema contained nested subschemas, it can be helpful to look at | |
| the specific part of the instance and subschema that caused each of the errors. | |
| This can be seen with the `ValidationError.instance` and | |
| `ValidationError.schema` attributes. | |
| With keywords like :kw:`anyOf`, the `ValidationError.context` | |
| attribute can be used to see the sub-errors which caused the failure. Since | |
| these errors actually came from two separate subschemas, it can be helpful to | |
| look at the `ValidationError.schema_path` attribute as well to see where | |
| exactly in the schema each of these errors come from. In the case of sub-errors | |
| from the `ValidationError.context` attribute, this path will be relative | |
| to the `ValidationError.schema_path` of the parent error. | |
| .. testcode:: | |
| for error in errors: | |
| for suberror in sorted(error.context, key=lambda e: e.schema_path): | |
| print(list(suberror.schema_path), suberror.message, sep=", ") | |
| .. testoutput:: | |
| [0, 'type'], {} is not of type 'string' | |
| [1, 'type'], {} is not of type 'integer' | |
| [0, 'type'], 3 is not of type 'string' | |
| [1, 'minimum'], 3 is less than the minimum of 5 | |
| [0, 'maxLength'], 'foo' is too long | |
| [1, 'type'], 'foo' is not of type 'integer' | |
| The string representation of an error combines some of these attributes for | |
| easier debugging. | |
| .. testcode:: | |
| print(errors[1]) | |
| .. testoutput:: | |
| 3 is not valid under any of the given schemas | |
| Failed validating 'anyOf' in schema['items']: | |
| {'anyOf': [{'type': 'string', 'maxLength': 2}, | |
| {'type': 'integer', 'minimum': 5}]} | |
| On instance[1]: | |
| 3 | |
| ErrorTrees | |
| ---------- | |
| If you want to programmatically query which validation keywords | |
| failed when validating a given instance, you may want to do so using | |
| `jsonschema.exceptions.ErrorTree` objects. | |
| .. autoclass:: jsonschema.exceptions.ErrorTree | |
| :noindex: | |
| :members: | |
| :special-members: | |
| :exclude-members: __dict__,__weakref__ | |
| .. attribute:: errors | |
| The mapping of validation keywords to the error objects (usually `jsonschema.exceptions.ValidationError`\s) at this level of the tree. | |
| Consider the following example: | |
| .. testcode:: | |
| schema = { | |
| "type" : "array", | |
| "items" : {"type" : "number", "enum" : [1, 2, 3]}, | |
| "minItems" : 3, | |
| } | |
| instance = ["spam", 2] | |
| For clarity's sake, the given instance has three errors under this schema: | |
| .. testcode:: | |
| v = Draft202012Validator(schema) | |
| for error in sorted(v.iter_errors(["spam", 2]), key=str): | |
| print(error.message) | |
| .. testoutput:: | |
| 'spam' is not of type 'number' | |
| 'spam' is not one of [1, 2, 3] | |
| ['spam', 2] is too short | |
| Let's construct an `jsonschema.exceptions.ErrorTree` so that we | |
| can query the errors a bit more easily than by just iterating over the | |
| error objects. | |
| .. testcode:: | |
| from jsonschema.exceptions import ErrorTree | |
| tree = ErrorTree(v.iter_errors(instance)) | |
| As you can see, `jsonschema.exceptions.ErrorTree` takes an iterable of `ValidationError`\s when constructing a tree so you can directly pass it the return value of a validator's `jsonschema.protocols.Validator.iter_errors` method. | |
| `ErrorTree`\s support a number of useful operations. The first one we | |
| might want to perform is to check whether a given element in our instance | |
| failed validation. We do so using the :keyword:`in` operator: | |
| .. doctest:: | |
| >>> 0 in tree | |
| True | |
| >>> 1 in tree | |
| False | |
| The interpretation here is that the 0th index into the instance (``"spam"``) | |
| did have an error (in fact it had 2), while the 1th index (``2``) did not (i.e. | |
| it was valid). | |
| If we want to see which errors a child had, we index into the tree and look at | |
| the `ErrorTree.errors` attribute. | |
| .. doctest:: | |
| >>> sorted(tree[0].errors) | |
| ['enum', 'type'] | |
| Here we see that the :kw:`enum` and :kw:`type` keywords failed for | |
| index ``0``. In fact `ErrorTree.errors` is a dict, whose values are the | |
| `ValidationError`\s, so we can get at those directly if we want them. | |
| .. doctest:: | |
| >>> print(tree[0].errors["type"].message) | |
| 'spam' is not of type 'number' | |
| Of course this means that if we want to know if a given validation | |
| keyword failed for a given index, we check for its presence in | |
| `ErrorTree.errors`: | |
| .. doctest:: | |
| >>> "enum" in tree[0].errors | |
| True | |
| >>> "minimum" in tree[0].errors | |
| False | |
| Finally, if you were paying close enough attention, you'll notice that | |
| we haven't seen our :kw:`minItems` error appear anywhere yet. This is | |
| because :kw:`minItems` is an error that applies globally to the instance | |
| itself. So it appears in the root node of the tree. | |
| .. doctest:: | |
| >>> "minItems" in tree.errors | |
| True | |
| That's all you need to know to use error trees. | |
| To summarize, each tree contains child trees that can be accessed by | |
| indexing the tree to get the corresponding child tree for a given | |
| index into the instance. Each tree and child has a `ErrorTree.errors` | |
| attribute, a dict, that maps the failed validation keyword to the | |
| corresponding validation error. | |
| best_match and relevance | |
| ------------------------ | |
| The `best_match` function is a simple but useful function for attempting | |
| to guess the most relevant error in a given bunch. | |
| .. doctest:: | |
| >>> from jsonschema import Draft202012Validator | |
| >>> from jsonschema.exceptions import best_match | |
| >>> schema = { | |
| ... "type": "array", | |
| ... "minItems": 3, | |
| ... } | |
| >>> print(best_match(Draft202012Validator(schema).iter_errors(11)).message) | |
| 11 is not of type 'array' | |
| .. autofunction:: best_match | |
| :noindex: | |
| .. function:: relevance(validation_error) | |
| :noindex: | |
| A key function that sorts errors based on heuristic relevance. | |
| If you want to sort a bunch of errors entirely, you can use | |
| this function to do so. Using this function as a key to e.g. | |
| `sorted` or `max` will cause more relevant errors to be | |
| considered greater than less relevant ones. | |
| Within the different validation keywords that can fail, this | |
| function considers :kw:`anyOf` and :kw:`oneOf` to be *weak* | |
| validation errors, and will sort them lower than other errors at the | |
| same level in the instance. | |
| If you want to change the set of weak [or strong] validation | |
| keywords you can create a custom version of this function with | |
| `by_relevance` and provide a different set of each. | |
| .. doctest:: | |
| >>> schema = { | |
| ... "properties": { | |
| ... "name": {"type": "string"}, | |
| ... "phones": { | |
| ... "properties": { | |
| ... "home": {"type": "string"} | |
| ... }, | |
| ... }, | |
| ... }, | |
| ... } | |
| >>> instance = {"name": 123, "phones": {"home": [123]}} | |
| >>> errors = Draft202012Validator(schema).iter_errors(instance) | |
| >>> [ | |
| ... e.path[-1] | |
| ... for e in sorted(errors, key=exceptions.relevance) | |
| ... ] | |
| ['home', 'name'] | |
| .. autofunction:: by_relevance | |
| :noindex: |