Skip to content

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

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.

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 lab: Model answer feeds Read the offer (Parse JSON); Price (Extract) and Headline (Text Expression) read the parsed offer. Photos feed Each photo (Enumerate JSON), collected by Keyframe map (Generate JSON, object shape) and checked by Check the map (Validate JSON Schema, madoo.keyframe-map/v1). Listing (Compose JSON) takes the headline, the price and the map.

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:

  1. Read the offer — Parse JSON with Find first takes the block out of the prose. Its inline schema requires the five fields, price a number and on_sale a 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 a json port directly; this is the bridge.

  2. 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. truncatewords cuts on a whole word — truncate would have cut inside "Villa Serena" — and · OFFER appears only while on_sale is true. Text Template could fill the names, not change their case, cut them or choose a word.

  3. Price — Extract takes price with Expected Type number: it leaves as the number 189, not as text.

  4. 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 named source.uri with 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.

  5. 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 (price a number, teaser an 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}}" became 189, 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 in headline, and the whole map went into teaser as 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.

Terminal window
node madoo-install-example.mjs lab-building-json --api <your Madoo API URL> --key <key.json> --run
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