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
204 changes: 203 additions & 1 deletion source/reference/array.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,209 @@ 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
unevaluated**properties** affect only **properties** in an object, only
micshar92 marked this conversation as resolved.
Show resolved Hide resolved
**item**-related keywords affect unevaluated**items**.
micshar92 marked this conversation as resolved.
Show resolved Hide resolved

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

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.
micshar92 marked this conversation as resolved.
Show resolved Hide resolved

..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 **or 2** and also 2." So anything that's
micshar92 marked this conversation as resolved.
Show resolved Hide resolved
not a 2 fails validation. That's because ``allOf``, ``anyOf``, and ``oneOf``
attach themselves to ``items`` in the same subschema.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we use "attach themselves to items" anywhere as a "technical" term, I'd probably try to use something closer to how I phrased it in the above comment (that items doesn't "see inside" oneOf).


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. ``allOf``, ``anyOf``, and ``oneOf`` do not attach
themselves to ``unevaluatedItems``.

You can also define ``unevaluatedItems`` as a boolean. In the next
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's nothing specifically unique about unevaluatedItems in this respect -- in JSON Schema (at least any modern version), booleans are valid schemas. I'm not saying it's not worth pointing out or showing an example, but I'd rework the language here.

Suggested change
You can also define ``unevaluatedItems`` as a boolean. In the next
Because booleans are valid schemas for any JSON Schema keyword, you can also prevent any additional items entirely by using `false` as the schema you provide to `unevaluatedItems`. In the next

example, let's use ``unevaluatedItems`` to make sure we have no values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
example, let's use ``unevaluatedItems`` to make sure we have no values
example, let's use ``unevaluatedItems`` to make sure we have no properties

outside of "SKU" and "product."

..schema_example::

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

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

This schema will always fail validation because "quantity" is required,
but it's outside of the ``allOf``, and ``unevaluatedItems`` *does not recognize any values outside of its own subschema*.
Here, ``unevaluatedItems`` considers anything outside of "SKU" and
"product" to be additional. Combining the schemas with ``allOf`` won’t
change that.

Instead, keep all your ``unevaluatedItems`` in the same list:
micshar92 marked this conversation as resolved.
Show resolved Hide resolved

..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, you can use ``unevaluatedItems`` if 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 reads inside the subschema it's attached to,
so if you want to add an item, add it inside that subschema.

You can also set ``unevaluatedItems`` in a nested tuple.

.. schema_example::

{
"description": "unevaluatedItems with nested tuple",
"schema": {
"prefixItems": [
{ "type": "string" }
],
"allOf": [
{
"prefixItems": [
true,
{ "type": "number" }
]
}
],
"unevaluatedItems": false
},
"tests": [
{
"description": "with no unevaluated items",
"data": ["foo", 42],
"valid": true
},
{
"description": "with unevaluated items",
"data": ["foo", 42, null],
"valid": false
}
]
}

In the first test, all the "data" values are evaluated, but in the
second test, the ``null`` value is a type not specified by
micshar92 marked this conversation as resolved.
Show resolved Hide resolved
``prefixItems``. It's therefore valid and ``true`` that
``unevaluatedItems`` returns ``false`` in the first test, and invalid
and ``false`` in the second test. In other words, it is valid that no
unevaluated items exist until something not matching the string/number
pattern shows up.

Lastly, here's an important note: ``unevaluatedItems`` can't see inside
cousins (a vertically adjacent item inside a separate pair of {curly
braces} with the same "parent"— ``anyOf``, ``if``, ``not``, or similar).
Such an instance always fails validation.
micshar92 marked this conversation as resolved.
Show resolved Hide resolved

.. schema_example::

{
"description": "unevaluatedItems can't see inside cousins",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"oneOf": [
{
"prefixItems": [ true ]
},
{ "unevaluatedItems": false }
]
},
"tests": [
{
"description": "always fails",
"data": [ 1 ],
"valid": false
}
]
}

.. 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 with ``unevaluatedItems``
- multiples nested ``if``/``then``s
- multiple nested instances of ``contains``
- ignoring non-array types
- ``not``
micshar92 marked this conversation as resolved.
Show resolved Hide resolved
- and more

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