Skip to content

unevaluatedProperties do not treat "in-place applicators" per the spec #1365

@hackowitz-af

Description

@hackowitz-af

unevaluatedProperies should be applied after validating all subschemas, such that ValidationErrors from the subschemas are raised/yielded before raising errors for unevaluatedProperies

From https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-unevaluatedproperties:

"properties", "patternProperties", "additionalProperties", and all in-place applicators MUST be evaluated before this keyword can be evaluated.

The reference to "in-place applicators" includes logical keywords like allOf and conditional keywords like if and then.

From this documentation, it proceeds that:

  • Properties from valid subschemas (recursively) are considered "evaluated"
  • Properties from invald subschemas are evaluated first, raising/yielding any ValidationErrors, before being considered "unevaluated".

This makes a significant difference when failing on the first error encountered, where the "unevaluated properties are not allowed" message is misleading for a property that is present in the schema but fails validation - especially when the validation error is nested deeply within the "unevaluated" property.

Environment

$ pip list
Package                   Version
------------------------- --------
attrs                     25.3.0
jsonschema                4.24.0
jsonschema-specifications 2025.4.1
pip                       25.1.1
referencing               0.36.2
rpds-py                   0.25.1
setuptools                80.3.1
typing_extensions         4.14.0
wheel                     0.45.1

Example

The (simplified) example schema attempts to define a generic "signal" schema, which may have different properties depending on it's "data type". This is arguably a bad idea but, good or bad, is possible in draft 2020-12:

{
  "$id": "signal.json",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "unevaluatedProperties": false,
  "required": ["source","destination","data type"],
  "properties": {
    "data type": {"enum": ["float","string"]},
    "source": true,
    "destination": true,
  },
  "allOf": [
    {
      "if": {"properties": {"data type": {"const": "float"}}},
      "then": {"properties": {"precision": {"type": "number"}}}
    },
    {
      "if": {"properties": {"data type": {"const": "string"}}},
      "then": {"properties": {"encoding": {"enum": ["ascii", "utf-8"]}}}
    }
  ]
}

This works as expected for well-formed objects:

validator = jsonschema.Draft202012Validator(schema)
validator.validate({"source": "A/C", "destination": "Store", "data type": "string", "encoding": "utf-8"})
validator.validate({"source": "A/C", "destination": "Store", "data type": "float", "precision": 64})

When a sub-schema gives unexpected results, the unevaluatedProperties error is wrongly printed, instead

validator.validate({"source": "A/C", "destination": "Store", "data type": "string", "encoding": "utf-16"})
ValidationError: Unevaluated properties are not allowed ('encoding' was unexpected)

Failed validating 'unevaluatedProperties' in schema:
    {'$id': 'signal.json',
     '$schema': 'https://json-schema.org/draft/2020-12/schema',
     'unevaluatedProperties': False,
     'required': ['source', 'destination', 'data type'],
     'properties': {'data type': {'enum': ['float', 'string']},
                    'source': True,
                    'destination': True},
     'allOf': [{'if': {'properties': {'data type': {'const': 'float'}}},
                'then': {'properties': {'precision': {'type': 'number'}}}},
               {'if': {'properties': {'data type': {'const': 'string'}}},
                'then': {'properties': {'encoding': {'enum': ['ascii',
                                                              'utf-8']}}}}]}

On instance:
    {'source': 'A/C',
     'destination': 'Store',
     'data type': 'string',
     'encoding': 'utf-16'}

This is not incorrect behavior, and a somewhat misleading message. The real problem is that utf-16 is not an allowed value, not that encoding is unevaluated. Only when we print all errors, we find the real (or at least original) error:

import traceback
for error in validator.iter_errors({"source": "A/C", "destination": "Store", "data type": "string", "encoding": "utf-16"}):
    traceback.print_exception(error)
jsonschema.exceptions.ValidationError: Unevaluated properties are not allowed ('encoding' was unexpected)

Failed validating 'unevaluatedProperties' in schema:
    {'$id': 'signal.json',
     '$schema': 'https://json-schema.org/draft/2020-12/schema',
     'unevaluatedProperties': False,
     'required': ['source', 'destination', 'data type'],
     'properties': {'data type': {'enum': ['float', 'string']},
                    'source': True,
                    'destination': True},
     'allOf': [{'if': {'properties': {'data type': {'const': 'float'}}},
                'then': {'properties': {'precision': {'type': 'number'}}}},
               {'if': {'properties': {'data type': {'const': 'string'}}},
                'then': {'properties': {'encoding': {'enum': ['ascii',
                                                              'utf-8']}}}}]}

On instance:
    {'source': 'A/C',
     'destination': 'Store',
     'data type': 'string',
     'encoding': 'utf-16'}
jsonschema.exceptions.ValidationError: 'utf-16' is not one of ['ascii', 'utf-8']

Failed validating 'enum' in schema['allOf'][1]['then']['properties']['encoding']:
    {'enum': ['ascii', 'utf-8']}

On instance['encoding']:
    'utf-16'

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions