Skip to content
This repository has been archived by the owner on Nov 3, 2023. It is now read-only.

add rough docs for unevaluatedItems #205

Merged
merged 9 commits into from
Jul 5, 2023
201 changes: 200 additions & 1 deletion source/reference/array.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,206 @@ Unevaluated Items

|draft2019-09|

Documentation Coming Soon
The ``unevaluatedItems`` keyword applies to any values not evaluated
by an ``items``, ``prefixItems``, or `contains` keyword. Just as
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved
``unevaluatedProperties`` affects only **properties** in an object,
``unevaluatedItems`` affects only **item**-related keywords.
micshar92 marked this conversation as resolved.
Show resolved Hide resolved

For this first example, let's assume you want to allow lists of items
that begin with either ``1`` or ``"A"``, but anything after must be ``2``.

..schema_example::

{
"oneOf": [{"prefixItems": [{"const": 1}]}, {"prefixItems": [{"const": "a"}]}],
"items": {"const": 2},
}
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

The logic here seems like it should be "one of either ``1`` or ``"A"``
and then ``2``." Actually, it's "either ``1`` or ``"A"`` *and also* ``2``."
And ``items`` expects a ``2`` here, so anything that's not a ``2``
fails validation. That's because ``items`` doesn't "see inside" any
instances of ``oneOf``, ``anyOf``, or ``allOf`` in the same subschema.

But if you replace ``items`` with ``unevaluatedItems``, it passes.
Anything that starts with ``1`` or ``A`` and then continues with a ``2``
is valid in that case.

Because booleans are valid schemas for any JSON Schema keyword, you can
also prevent additional items by setting ``unevaluatedItems`` to
``false.`` In the next example, let's use ``unevaluatedItems`` to make
sure we allow no properties besides ``SKU`` and ``product``.

.. note::
Watch out! The word "unevaluated" *does not* mean "not evaluated by
``items``, ``prefixItems``, or ``contains``." "Unevaluated" means
"not successfully evaluated", or "doesn't evaluate to true".
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

..schema_example::

{
"allOf": [
{
"type": "array",
"items": {
"SKU": "number",
"product": "string",
},
"unevaluatedItems": false
}
],

"items": {
"quantity": { "enum": ["1", "2", "3"] }
},
"required": ["quantity"],
}
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

This schema will always fail validation because ``quantity`` is required
but it's outside the ``allOf``, and ``unevaluatedItems``
*does not see any items outside its own subschema*. Here,
``unevaluatedItems`` considers anything outside of ``SKU`` and ``product``
to be additional.

Instead, keep all your ``unevaluatedItems`` in the same subschema:

..schema_example::

{
"items": {
"SKU": "number",
"product": "string",
"quantity": { "enum": ["1", "2", "3"] }
},
"required": ["quantity"],
"unevaluatedItems": false
}
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

Similarly, ``unevaluatedItems`` can't see inside cousins (vertically
adjacent properties inside a separate pair of {curly braces} with the
same "parent"— ``anyOf``, ``if``, ``not``, or similar). For instance,
in the example below, the ``unevaluatedItems`` doesn't "see inside" the
``prefixItems`` cousin before it. So since ``"prefixItems": [ true ]``
matches only length 1 arrays, and ``{ "unevaluatedItems": false }``
matches only empty arrays, all instances fail validation.

.. schema_example::

{
"schema": {
"oneOf": [
{
"prefixItems": [ true ]
},
{ "unevaluatedItems": false }
]
},
"tests": [
{
"description": "always fails",
"data": [ 1 ],
"valid": false
}
]
}
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved


You can also use ``unevaluatedItems`` when you're `structuring`.
Let's make a "half-closed" schema: something useful when you want to
keep the first two arguments, but also add more in certain situations.
("Closed" to two arguments in some places, "open" to more arguments
when you need it to be.)

.. schema_example::

{
"$id": "https://example.com/my-tuple",

"type": "array",
"prefixItems": [
true,
micshar92 marked this conversation as resolved.
Show resolved Hide resolved
{ "type": "boolean" }
],

"$defs": {
"closed": {
"$anchor": "closed",
"$ref": "#",
"unevaluatedItems": false
}
}
}

Then we can extend the tuple with another value:

.. schema_example::

{
"$id": "https://example.com/my-extended-tuple",

"$ref": "https://example.com/my-tuple",
"prefixItems": [
true,
true,
{ "type": "boolean" }
],
"unevaluatedItems": false
}

With this, you can use ``$ref`` to reference the first two
``prefixItems`` and keep the schema "closed" to two arguments when
you need it, "open" to more arguments when you need it. A reference to
``/my-tuple#closed`` would disallow more than two items.
``unevaluatedItems`` only sees inside its own subschema, so if you
want to add an item, add it inside that subschema.

This means you can also put ``unevaluatedItems`` in a nested tuple.

.. schema_example::

{
"schema": {
"prefixItems": [
{ "type": "string" }
],
"allOf": [
{
"prefixItems": [
true,
{ "type": "number" }
]
}
],
"unevaluatedItems": false
},
"tests": [
{
"description": "with no unevaluated items",
"data": ["foo", 42],
"valid": true
},
{
"description": "with an unevaluated item",
"data": ["foo", 42, null],
"valid": false
}
]
}
jdesrosiers marked this conversation as resolved.
Show resolved Hide resolved

In the first test, all the ``data`` values are evaluated, but in the
second test, a third value exists. ``prefixItems`` contrains only two
items, and ``unevaluatedItems`` applies only to those two.

.. note::
For a tall list of more examples, read our `unevaluatedItems Test Suite <https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft2020-12/unevaluatedItems.json>`_ on GitHub.
We test a lot of use cases there, including uncommon ones. Do any
of these apply to your schema?
- ``unevaluatedItems`` nested inside another ``unevaluatedItems``
micshar92 marked this conversation as resolved.
Show resolved Hide resolved
- ``if/then/else`` statements interacting with ``unevaluatedItems``
- nested ``if/then/else`` statements interacting with ``unevaluatedItems``
- ``unevaluatedItems`` ignoring non-arrays
- ``unevaluatedItems`` interacting with the ``not`` keyword
- and more

.. index::
single: array; contains
Expand Down