For-each loops
Use forEach when the same decision must be made for every item in a list your application supplies. A single request can answer a list of checks, classify several records or choose a route for each question.
An ordinary array lets Struct generate items within your schema's bounds. Struct's x-runtime.forEach extension produces one result for each supplied item, in the same order. x-runtime.bind supplies constants or descriptions from that item.
Example: check product features
The input is a product description. The request's state supplies two checks: whether the product is waterproof and whether it has a hood. Each result keeps the check's ID and adds a Boolean answer.
Save these files beside your client:
{
"$schema": "urn:blatent:json-schema:template-draft-v1",
"type": "array",
"maxItems": 20,
"x-runtime": {
"forEach": {
"ref": "state/checks",
"as": "check"
}
},
"items": {
"type": "object",
"properties": {
"id": {
"x-runtime": {
"bind": {
"const": {
"ref": "check/id"
}
}
}
},
"answer": {
"type": "boolean",
"x-runtime": {
"bind": {
"description": {
"ref": "check/description"
}
}
}
}
},
"required": [
"id",
"answer"
],
"additionalProperties": false
}
}
{
"checks": [
{
"id": "waterproof",
"description": "True if the product is waterproof; false otherwise."
},
{
"id": "hooded",
"description": "True if the product has a hood; false otherwise."
}
]
}
The $schema keyword is standard, but the urn:blatent:json-schema:template-draft-v1 value selects Struct's template dialect. Send this schema in the ordinary schema request field, with the checks in state; no separate registration step is needed.
Before you run this example
Call https://api.blatent.ai from your server. Set your API key in the same terminal where you run the example:
export BLATENT_API_KEY='YOUR_SERVICE_KEY'
Python requires Python 3.10+ and HTTPX:
python -m pip install "httpx>=0.28,<1"
TypeScript requires Node.js 24+ and uses its built-in fetch. The cURL examples require curl and jq.
Save the displayed files and the selected client in one directory. Run the client there with python example.py, node example.ts or bash example.sh. Downloads use the example's name in place of example.
- Python
- TypeScript
- cURL
import json
import os
from pathlib import Path
import httpx
request = {
"input": "A lightweight waterproof jacket with no hood.",
"schema": json.loads(Path("product-checks.schema.json").read_text(encoding="utf-8")),
"state": json.loads(Path("product-checks.state.json").read_text(encoding="utf-8")),
}
with httpx.Client(
base_url="https://api.blatent.ai",
headers={"Authorization": f"Bearer {os.environ['BLATENT_API_KEY']}"},
timeout=195,
) as client:
response = client.post("/v1/resolve", json=request)
response.raise_for_status()
print(json.dumps(response.json()["output"]))
import { readFileSync } from "node:fs";
const request = {
"input": "A lightweight waterproof jacket with no hood.",
"schema": JSON.parse(readFileSync("product-checks.schema.json", "utf8")),
"state": JSON.parse(readFileSync("product-checks.state.json", "utf8")),
};
const response = await fetch(
"https://api.blatent.ai" + "/v1/resolve",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLATENT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(request),
signal: AbortSignal.timeout(195_000),
},
);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(JSON.stringify((await response.json()).output));
set -eu
body=$(jq -n --argjson body '{"input":"A lightweight waterproof jacket with no hood."}' \
--slurpfile f0 'product-checks.schema.json' \
--slurpfile f1 'product-checks.state.json' \
'$body | setpath(["schema"]; $f0[0]) | setpath(["state"]; $f1[0])')
curl --fail-with-body --silent --show-error \
"https://api.blatent.ai/v1/resolve" \
-H "Authorization: Bearer $BLATENT_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary "$body"
Illustrative output: [{"id":"waterproof","answer":true},{"id":"hooded","answer":false}].
How the loop binds each item
| Part | Meaning |
|---|---|
forEach.ref | The supplied array to iterate: state/checks |
forEach.as | A local name for its current item: check |
items | The result schema repeated for each item |
bind.const | Set a field to an exact value from that item |
bind.description | Supply that item's instructions for a generated field |
check/id fixes the result's ID. Struct does not infer or rewrite it. check/description supplies the description for answer, so each Boolean answers its own check against the same product input.
Binding happens before generation. bind.const fixes an exact value; bind.description supplies instruction text, not a guaranteed answer. The loop's length and IDs come from state; Struct chooses the Boolean answers.
Change the list, keep the schema
Add, remove or reorder checks in the state file without changing the schema. Reversing the two checks reverses their output order. An empty checks array returns [].
This example permits at most 20 checks through maxItems. The referenced value must exist and be an array within the declared bounds. A missing or wrongly typed source is an error, not an empty list.
Loop references can read state, parameters and an enclosing loop's item. They read supplied data, not generated output. These ref values select data; a JSON Schema $ref reuses a schema definition. Keep binding facts and per-item instructions under your application's control.
Loops and branching
A loop repeats a result structure; it does not choose among the source items. Each result can have its own conditions or switch when its fields depend on one another.
Start with a simple item schema and a short list. Use nested loops only when the supplied data itself has nested lists.