Skip to content

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

Closed as not planned
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions