JSON and templates
The versioned JSON document format, data-driven templates with placeholders and $each, and the JSON Schema they are validated against.
Both need the serde feature on the lightweight-pdf crate. The CLI and the
JavaScript package have it built in.
JSON documents
Document::from_json(&str) and .to_json() (de)serialise the whole document tree. The root is
versioned:
{
"schema_version": 1,
"document": {
"page_format": "A4",
"margin": { "top": 40.0, "right": 40.0, "bottom": 40.0, "left": 40.0 },
"metadata": { "title": "Rechnung RE-2026-0100" },
"children": [
{ "type": "text", "content": "Rechnung RE-2026-0100", "style": { "size": 24.0, "font": "sans-bold" }, "outline_level": 1 },
{ "type": "table_of_contents" },
{ "type": "list", "items": [{ "marker": "bullet", "content": { "type": "text", "content": "Zahlbar innerhalb 14 Tagen" } }] }
]
}
}
(Shortened from examples/document.json.)
- Every element is tagged by a
"type"field ("text","table","list", …). - Unknown fields anywhere in the tree are a parse error, never silently dropped.
- Images embed as base64:
{"bytes_base64": "...", "common": {...}}. - Header and footer are Rust closures and cannot be represented;
to_json()refuses if either is set rather than dropping them. - The same
Deserializeimplementation works with other formats (YAML, TOML) throughserde_json::from_str::<DocumentSchema>and friends – bring your own crate.
It is a serialisation format, not a template language: no scripting, expressions or loops.
Templates
Document::from_template(template_json, data_json, MissingPlaceholder) resolves placeholders in a
template document against a separate data document – no Rust code needed for the content:
{
"type": "table",
"rows": [
{
"$each": "invoice.items",
"template": [
{ "element": { "type": "text", "content": "{{description}}" } },
{ "element": { "type": "text", "content": "{{amount}}" } }
]
}
]
}
"{{path.to.value}}"as the entire string value resolves to the data’s own JSON type – a number stays a number. Embedded in more text, it is always a string.- A missing path is an error by default;
MissingPlaceholder::Emptyresolves it to an empty string instead. {"$each": "items", "template": <value>}as an array element expands to one copy oftemplateper element ofitems. Inside, the element’s own fields resolve first, then the outer data.- Deliberately array iteration only: no conditions, expressions or filters.
render_template()resolves a template without building aDocument, for other consumers of the same JSON.
examples/invoice-template.json and examples/invoice-data.json are a complete pair; the
showcase renders them with the CLI on every site build.
JSON Schema
The schemars feature generates a JSON Schema for the document and template format from the Rust
types. lwpdf schema prints it, and the JavaScript package’s TypeScript types are generated from
it – nothing is maintained by hand.