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
188 changes: 187 additions & 1 deletion source/reference/array.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,193 @@ 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
``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".

.. schema_example::

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

"items": {
"properties": {
"quantity": { "enum": ["1", "2", "3"] }
},
"required": ["quantity"]
}
}

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": {
"properties": {
"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. All instances fail vallidation
because ``"prefixItems": [ true ]`` matches only length 1 arrays, and
``{ "unevaluatedItems": false }`` matches only empty arrays.

.. schema_example::

{
"allOf": [
{ "prefixItems": [true] },
{ "unevaluatedItems": false }
]
}
--X
[1]

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::

{
"prefixItems": [
{ "type": "string" }
],
"allOf": [
{
"prefixItems": [
true,
{ "type": "number" }
]
}
],
"unevaluatedItems": false
}
--
["foo", 42]
--X
["foo", 42, null]

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.

Here are some of our examples in the suite:
* ``unevaluatedItems`` nested inside another ``unevaluatedItems``
* ``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
micshar92 marked this conversation as resolved.
Show resolved Hide resolved

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