Walkthrough
One feed, four runs.
A commodity futures feed, mapped from one workbook and run four times. The first run is the happy path. The other three show what the engine writes when something is wrong: a typo in the sheet, a value that fails a rule, and a field that throws. In every case the fix is a workbook edit, not a release.
Run 1 · happy path
A row in the sheet becomes a field in the feed.
The target columns are indented. That indentation is the nesting of the output. Beside the feed the engine writes a second file in the same shape that says, for every field, what it read.
| T1 | T2 | T3 | T4 | Target expression |
|---|---|---|---|---|
| Execution | ||||
| TradeID | trade.tradeId | |||
| Economics | ||||
| Price | trade.tradePrice | |||
| Amount | quantity * (buySell == 'Sell' ? -1 : 1) | |||
| Identifier | ||||
| Exchange | getExchange(instrument.exchange) ?: instrument.exchange | |||
| MonthCode | instrument.ticker.replaceFirst('^[A-Z]+', '').substring(0, 1) |
valid.json · what the consumer receives"Execution": {
"TradeID": "T-20931",
"Economics": {
"Price": "81.82",
"Amount": -1000.0,
"Identifier": {
"Exchange": "XNYM",
"MonthCode": "Z"
}
}
}
valid.source.json · why each field holds that value"Execution": {
"TradeID": "trade.tradeId=T-20931",
"Economics": {
"Price": "trade.tradePrice=81.82",
"Amount": "quantity=1000.0, buySell=Sell",
"Identifier": {
"Exchange": "instrument.exchange=NYM,
getExchange()=XNYM",
"MonthCode": "instrument.ticker
.replaceFirst(...)=Z"
}
}
}
The source file has the same tree as the feed. An analyst lines them up without a developer and without a debugger.
Amount read a quantity and a side. Exchange read a raw code and the lookup that mapped it. Lookup and reference results are inputs too.
This is not reconstructed on request. It is written beside the feed every time the batch runs.
Run 2 · refused workbook
A typo in the sheet never reaches the data.
The source columns declare what exists. When the workbook loads, the engine builds a typed model of that shape and checks every expression against it: property names, method names, argument types. A misspelt path is refused with the row number before a single row is read.
| S1 | S2 | Source type |
|---|---|---|
| trade | ||
| tradeId | String | |
| tradeExecutionDateTime | Timestamp | |
| buySell | String | |
| quantity | Double | |
| tradePrice | Double |
Row 7, changed on purpose
- Target
- Execution.TradeID
- Target expression, before
- trade.tradeId
- Target expression, after
- trade.tradeId2
run.sh validate · refused$ run.sh validate
::validate: line=7
The property [trade.tradeId2]
is undeclared
No rows were read.
No file was written.
Today a typo like this is a failed job somewhere in a pipeline. Here it is a one-line refusal with a row number, before anything flows.
The same check catches a wrong method, a wrong argument type and a class that is not on the list.
Correct row 7 and run again. Nobody opens a build.
Run 3 · failed rule
It says which field failed, and why.
The exchange lookup sheet has no row for one venue code, so the raw code passes through. The validation rule beside the field catches it as the record is produced. The record lands in the invalid file, and the reason file names the field.
The row in the workbook
- Target
- Identifier.Exchange
- Target expression
- getExchange(instrument.exchange)
?: instrument.exchange - Validation expression
- Exchange == 'NYM'
? 'should be mapped to XNYM' : ''
invalid.json · the document"Identifier": {
"Type": "ExchangeCode",
"Exchange": "NYM",
"Ticker": "CL",
"MonthCode": "U",
"YearCode": "2026"
}
invalid.validation.json · why"Identifier": {
"Type": "",
"Exchange":
"should be mapped to XNYM",
"Ticker": "",
"MonthCode": "",
"YearCode": ""
}
Today validation happens after the fact, somewhere else. Here it runs on the field, with a message an analyst wrote and can act on.
Every other field in the record is present and correct. The one that failed is the one that is named.
One row in the lookup sheet: NYM maps to XNYM. The next run passes.
Run 4 · exception
One bad field does not sink the record.
The instrument lookup finds nothing for one trade, so the reference variable is null and the safe navigation in the expression sits one hop too late. The field throws. Everything else in the record still maps, and the exception names the field, not a stack frame.
The row in the workbook
- Target
- Economics.Amount
- Target expression
- security.futureInformation?
.contractSize == null
? null
: quantity / security
.futureInformation.contractSize - The fix, one hop earlier
- security?.futureInformation?.contractSize
error.json · the document"TradeID": "T-20944",
"Economics": {
"Type": "CommodityFuture",
"CommodType": null,
"Amount": null,
"Price": "34.11",
"Identifier": {
"Exchange": "IFED",
...
}
}
error.exception.json · why"Economics": {
"Type": "",
"CommodType": "",
"Amount": "EvaluationException:
Cannot get property
'futureInformation' on null",
"Price": "",
...
}
The message says which field, which expression and which null. The trade ID, the price and the exchange all mapped.
Valid, invalid with the rule it failed, or error with the field that threw. Nothing is silent.
Move the safe navigation one hop earlier. A workbook edit and a review, not a release.
One run, three files
Run this beside your own feed.
Pick one feed. We map it from the table you already expose, run it for one cycle, and compare the two outputs field by field. You keep the workbook either way.