Building JSON
Some steps want a JSON document, not a text: Keyframes to Video reads a keyframe map, a webhook expects a body, a template list reads one object per item. The values come from other steps — a photo, a price computed by Calculate, a title written by a model. Three nodes build the document, by where the values come from:
| The values are | Node |
|---|---|
| a few values of one item, placed in a fixed document | Compose JSON (utility/json_compose) |
| one value per item of an iterated branch, collected into a list (or an object around it) | Generate JSON (aggregate/json) — see Iteration |
| already a JSON document, written as text by a model or a file | Parse JSON (utility/json_parse) — see Reading JSON |
Compose JSON
Section titled “Compose JSON”Write the document once, in Template, with {{name}} in its strings. Each distinct name becomes an input of
the node, of any type, to connect:
{ "schema": "madoo.keyframe-map/v1", "keyframes": [ { "source": { "kind": "image_uri", "uri": "{{first_photo}}" } }, { "source": { "kind": "image_uri", "uri": "{{second_photo}}" } } ], "caption": "{{village}} — from {{price}} EUR"}How a value enters depends on where the placeholder stands:
| Placeholder | What is written | Example |
|---|---|---|
a whole string: "{{price}}" |
the value with its type: a number stays a number, a boolean a boolean, a list or an object as it is | "price": 485 |
| a whole string, and the input is a file (an image, a video, a PDF) | the file’s stored reference, which media fields of Madoo contracts read | "uri": "…/photo.jpg" |
inside a longer string: "from {{price}} EUR" |
the value as text — a text as it is, anything else as compact JSON — escaped | "caption": "Manarola — from 485 EUR" |
Escaping is the point: a title with quotes or a line break (Il "borgo" più bello) is written correctly, where in
Text Template it would break the JSON, and a number never turns into the text "485". Placeholders are read in
strings only, not in keys. A name is a letter followed by letters, digits or _, at most 32 per node.
In an iterated branch Compose JSON runs once per item and builds one document each; collect them with Generate
JSON and a column of type json.
Checking the result
Section titled “Checking the result”Compose JSON can check what it built: Schema Source catalog with a Madoo contract (get_json_schema lists them,
for example madoo.keyframe-map/v1), or inline with a schema written in the node — as in
Validate JSON Schema. With On Invalid Document fail (default) a document that breaks the schema
stops the step with the reasons; with route the branch that reads json is skipped and valid and errors drive
a correction branch. Without a schema, valid is always true.
The lab: a hotel offer, from a model’s answer to the listing
Section titled “The lab: a hotel offer, from a model’s answer to the listing”A hotel on Lake Como sells a weekend offer. A model has written the offer card — as models do, a sentence before, a
```json block, a sentence after — and the hotel has three photos for a short teaser. The booking site wants one
JSON document: the headline, the price as a number, and the keyframe map of the teaser. Four nodes of this page and
its neighbours build it, with no AI step and no credits.
The model’s answer, as it arrives:
Here is the offer card you asked for. I kept the title close to the brochure.
```json{ "hotel": "Villa Serena", "town": "Bellagio", "title": "Two nights at the \"Villa Serena\" with breakfast on the terrace", "price": 189, "on_sale": true}```
Let me know if you want a shorter title for the social posts.Step by step, and why each node is the right one:
-
Read the offer — Parse JSON with Find
firsttakes the block out of the prose. Its inline schema requires the five fields,pricea number andon_salea boolean: an answer that forgot the price, or wrote"189", stops here with the reason instead of producing a listing with a hole. A text cannot enter ajsonport directly; this is the bridge. -
Headline — Text Expression reads the parsed offer by member, so one input carries every field:
{{ offer.hotel | string.upcase }} · {{ offer.town }} — {{ offer.title | string.truncatewords 6 }}{{ if offer.on_sale }} · OFFER{{ end }}It gives
VILLA SERENA · Bellagio — Two nights at the "Villa Serena"... · OFFER.truncatewordscuts on a whole word —truncatewould have cut inside"Villa Serena"— and· OFFERappears only whileon_saleis true. Text Template could fill the names, not change their case, cut them or choose a word. -
Price — Extract takes
pricewith Expected Typenumber: it leaves as the number 189, not as text. -
Keyframe map — the photos fan out (Enumerate JSON, one item per photo) and Generate JSON collects them with the object shape: Array Key
keyframes, Root Fields{"schema": "madoo.keyframe-map/v1"}, Item Fields{"source": {"kind": "image_uri"}}and one image column namedsource.uriwith Nested Names on. Check the map validates it against the contract Keyframes to Video reads, so a wrong map is caught here, before a paid video step. -
Listing — Compose JSON writes the document for the site:
{ "headline": "{{headline}}", "price": "{{price}}", "currency": "EUR","caption": "From {{price}} EUR a night", "teaser": "{{keyframe_map}}" }and checks it against a schema written in the node (
pricea number,teaseran object).
The run, on Dev, cost 0 credits:
{ "headline": "VILLA SERENA · Bellagio — Two nights at the \"Villa Serena\"... · OFFER", "price": 189, "currency": "EUR", "caption": "From 189 EUR a night", "teaser": { "schema": "madoo.keyframe-map/v1", "keyframes": [ { "source": { "kind": "image_uri", "uri": "…/photo/json_0.jpg" } }, { "source": { "kind": "image_uri", "uri": "…/photo/json_1.jpg" } }, { "source": { "kind": "image_uri", "uri": "…/photo/json_2.jpg" } } ] }}Three things to notice, each the reason Compose JSON exists:
"price": "{{price}}"became189, a number: a placeholder that is the whole string keeps the value’s type.- In
"From {{price}} EUR a night"the same value became text inside the sentence. - The quotes of
"Villa Serena"arrived escaped inheadline, and the whole map went intoteaseras an object. Written with Text Template, the quotes would have broken the JSON and the map would have been a string.
The three photos travel with the example; the run takes a few seconds and costs nothing.
node madoo-install-example.mjs lab-building-json --api <your Madoo API URL> --key <key.json> --runWhen a step fails
Section titled “When a step fails”| Code | Meaning | Fix |
|---|---|---|
TEXT_PLACEHOLDER_UNCONNECTED (validation) |
a {{name}} of the template has no input connected |
connect a value to that input, or remove the placeholder |
JSON_COMPOSE_MISSING |
an input had no value at run time; the message names the place, e.g. $.keyframes[0].source.uri |
check the step that should produce it |
JSON_COMPOSE_TEMPLATE_INVALID |
Template is not valid JSON | write a JSON document; placeholders go inside strings: "{{price}}" |
JSON_COMPOSE_SCHEMA_INVALID |
the document breaks the schema (mode fail); the message lists where and why |
fix the template or the values, or use route |
invalid_dynamic_config (validation) |
Template is not JSON, or a placeholder name is reserved | as above |