Formats — read one, write another
The question for this lesson: your data arrives as CSV, but the system downstream wants JSON. Or a partner sends a fixed-width mainframe extract, or an HL7 healthcare message. How does one pipeline deal with so many file formats?
The answer is small: a source or output node’s type: names its format, and everything
between them is the same format-agnostic record model. So reading one format and writing
another is a one-word change.
What you’ll be able to do
Section titled “What you’ll be able to do”- Change a pipeline’s input or output format by changing a node’s
type:. - Name the formats Clinker reads and writes.
- Avoid two format traps: CSV never infers types, and NDJSON is a
jsonoption rather than its own format.
The format lives in type:
Section titled “The format lives in type:”A source’s type: is the format it reads; an output’s type: is the format it writes. Here a
pipeline reads CSV and writes CSV. Run it, and read what comes out.
> output appears here — predict, then run
Now the format point, hands-on: in the runner above, change the output node’s type: csv
to type: json and run again. Same rows, same pipeline, but the result comes out as JSON
records instead of CSV lines. Nothing else changed, because the rows flowing between the nodes
never depended on the format at either end.
🌱 New here? — format-agnostic records
Once a source reads a file, every row becomes the same internal record (named, typed cells, the schema from lesson 01). Transforms, routes, and aggregates work on that, never on raw bytes, which is exactly why swapping the input or output format doesn’t touch the rest of the pipeline.
The format catalog
Section titled “The format catalog”Clinker reads and writes all of these; you select one with type::
type: | Format | Typical use |
|---|---|---|
csv | comma/delimited text | the everyday default |
json | JSON records | APIs, document stores (NDJSON is a json option) |
xml | XML documents | legacy systems, configuration feeds |
fixed_width | columns by character position | mainframe extracts (needs declared field widths) |
x12, edifact | EDI business documents | invoices, purchase orders between companies |
hl7 | HL7 messages | healthcare/clinical data |
The EDI and HL7 formats are real and stable (with some charset/nesting limits) but specialized,
so you’ll reach for them only in those industries. csv and json cover most day-to-day work.
(There’s also a swift banking format, but it’s a preview, only message blocks 1–5, so
treat it as not-yet-ready.)
Clinker’s real order_fulfillment pipeline shows the everyday case: one CSV source, and two
outputs in different formats, a CSV of fulfilled orders and a JSON priority report:
- type: output name: fulfilled_orders input: route_priority config: { name: fulfilled_orders, type: csv, path: ./output/fulfilled_orders.csv }
- type: output name: priority_report input: route_priority config: { name: priority_report, type: json, path: ./output/priority_report.json }Two format traps
Section titled “Two format traps”Create: one source, two output formats
Section titled “Create: one source, two output formats”Author the output stage yourself. Because records are format-agnostic once read, one source can
feed several outputs in different formats, exactly what the order_fulfillment example does.
Write two output nodes that read the same upstream node and emit the same rows as CSV and JSON:
- type: output name: as_csv input: customers config: { name: as_csv, type: csv, path: ./output/customers.csv }- type: output name: as_json input: customers config: { name: as_json, type: json, path: ./output/customers.json }Both read input: customers, so both receive every row; only the type: differs. The CSV lands
as delimited rows; the JSON lands as an array of records:
[{"status":"active","lifetime_value":"80000"},{"status":"inactive","lifetime_value":"1200"},{"status":"active","lifetime_value":"4000"}]Nothing upstream changes; the rows never depended on the format at either end. Verify with
clinker run your_pipeline.yaml --dry-run (it resolves both output paths) and --explain.