Specification scope: Core with the Validation companion specification.
How do you require an array of sensor records to include at least one alarm? Typing the elements does not require an alarm to be present.
In JSON Structure, items describes every element. The validation companion’s
contains, minContains, and maxContains inspect the collection and count
elements that match another schema. Confusing those jobs leaves either the
array contents or the alarm count underspecified.
items applies to every element
Consider a sensor batch containing ordinary readings and alarm records. Every
entry has a timestamp, a kind, and a numeric value. A choice
type uses kind to select the concrete record shape, and items requires every
array element to be one of those records.
The schema uses contains to identify alarms and requires one to
three matches:
{
"$schema": "https://json-structure.org/meta/validation/v0/#",
"$id": "https://example.com/schemas/sensor-batch",
"name": "SensorBatchSchema",
"$uses": ["JSONStructureValidation"],
"$root": "#/definitions/SensorBatch",
"definitions": {
"SensorRecord": {
"name": "SensorRecord",
"abstract": true,
"type": "object",
"properties": {
"timestamp": { "type": "datetime" },
"value": { "type": "double" }
},
"required": ["timestamp", "value"]
},
"ReadingRecord": {
"name": "ReadingRecord",
"type": "object",
"$extends": "#/definitions/SensorRecord",
"properties": {
"kind": { "type": "string" }
}
},
"AlarmRecord": {
"name": "AlarmRecord",
"type": "object",
"$extends": "#/definitions/SensorRecord",
"properties": {
"kind": { "type": "string" },
"code": { "type": "string" }
},
"required": ["kind", "code"]
},
"BatchRecord": {
"name": "BatchRecord",
"type": "choice",
"$extends": "#/definitions/SensorRecord",
"selector": "kind",
"choices": {
"reading": { "type": { "$ref": "#/definitions/ReadingRecord" } },
"alarm": { "type": { "$ref": "#/definitions/AlarmRecord" } }
}
},
"SensorBatch": {
"name": "SensorBatch",
"type": "array",
"items": {
"type": { "$ref": "#/definitions/BatchRecord" }
},
"contains": {
"type": "object",
"properties": {
"kind": { "type": "string", "const": "alarm" }
},
"required": ["kind"]
},
"minContains": 1,
"maxContains": 3
}
}
}
A batch with two alarms satisfies the one-to-three alarm rule:
[
{
"kind": "reading",
"timestamp": "2026-10-14T09:00:00Z",
"value": 71.2
},
{
"kind": "alarm",
"timestamp": "2026-10-14T09:00:05Z",
"value": 92.8,
"code": "HIGH_TEMP"
},
{
"kind": "alarm",
"timestamp": "2026-10-14T09:00:07Z",
"value": 18.1,
"code": "LOW_PRESSURE"
}
]
Count matches, not positions
The draft defines contains for arrays and sets. At least one element must
satisfy its schema. minContains raises that lower bound; maxContains adds an
upper bound. Both bounds are non-negative integers and count elements that
satisfy contains. They do not constrain total collection length.
In the example, a batch of 100 records may be valid if one, two, or three are
alarms. Four alarms violate maxContains. Zero alarms violate both the basic
contains requirement and minContains: 1.
Order is irrelevant to the count. The matching records may appear anywhere,
and one element either contributes one match or none. contains does not define a
second item type. Matching elements must still satisfy items, so every alarm
must be a valid BatchRecord.
What each keyword constrains
Without items, contains says nothing about nonmatching elements. A string,
an unrelated object, or null could coexist with a matching alarm unless some
other rule excludes it. That may be intentional for a heterogeneous collection,
but it is not an item-type declaration.
Conversely, items alone can require valid sensor records but cannot say that
the batch actually contains an alarm, much less bound the alarm count.
For this batch, every element must satisfy items, and between one and three of
those elements must also satisfy contains. Remove either clause and the rule
changes. Treating contains as an item type is therefore not shorthand;
it leaves every nonmatching element without an item-type constraint.