1.What is xlli?
xlli is a JSON format for spreadsheet data. One file extension (.xlli), one MIME type (application/vnd.xlli+json), one canonical form readable in any language with a JSON parser.
The first public dictionary is MicroTable — a simple grid kind for the cases where a spreadsheet would be overkill but a CSV is too plain. Headers, styled cells, sparklines and badges as cell types, sparse overrides for hand-edited content. About thirty lines of JSON for a real table.
xlli is designed to round-trip cleanly with Excel via .NET (EPPlus), and to be implementable in a weekend by a competent engineer in any language. The reference engines are C# and Rust. The Rust engine compiles to WASM for the browser and Node, and exposes a native binding for Python via PyO3. Two engines in lockstep, multiple language runtimes, one canonical JSON.
2.Why xlli?
Four things motivate the format. Take any one of them and a thoughtful engineer would build something like xlli. We hit all four in actual use, which is why we're building it now.
2.1128-bit decimal
JSON numbers are IEEE 754 doubles. That's fine for charts and dashboards. It's not fine for finance. 0.1 + 0.2 != 0.3 is the canonical example — amusing in a tutorial, catastrophic in a P&L.
xlli encodes high-precision values as tagged objects:
{ "value": "1234567890.123456789",
"type": "decimal" }
The string carries the exact decimal. The tag tells consumers it isn't a display string. C#'s System.Decimal, Rust's decimal crate, and IBM's libdecnumber all round-trip through this encoding losslessly. v0.2 ships this; v0.1 sticks to JSON-native numbers.
2.2JSON sheet persistence
Spreadsheet files have always been binary or zipped-XML — a black box between sessions. xlli is plain JSON. You can cat it, grep it, diff it, version-control it cleanly, edit it with any text editor, transform it with jq.
A row added becomes one line of git diff, not a binary churn. Same JSON in C# as in Rust as in JS. Same bytes on disk as on the wire. Stable canonical ordering, byte-identical round-trip, explicit precision, no hidden state.
2.3Excel ⇄ xlli ⇄ HTML
Three formats that should talk to each other and rarely do. xlli sits in the middle as the canonical structured form:
- Excel → xlli: read
.xlsxvia EPPlus. v0.2. - xlli → Excel: write
.xllito.xlsx. v0.2. - xlli → HTML: render to a styled HTML table. v0.1.
- HTML → xlli: parse a structured HTML table back. Future.
xlli's contribution isn't a fifth format — it's the explicit lossless intermediate that lets the existing formats talk.
2.4Two engines, native everywhere
xlli has two reference engines: one in C#, one in Rust. They're maintained in lockstep, validated by golden-file tests that produce byte-identical canonical JSON.
The Rust engine compiles to WASM and ships as the JavaScript package — a thin wrapper that gives you ergonomic JS APIs over the canonical engine. Same engine reaches Python via PyO3, with NumPy-friendly interop. C# stays its own native engine because the .NET ecosystem and EPPlus integration both deserve a first-class library, not a wrapper.
Two engines, multiple language runtimes, one canonical JSON. Adding a fifth language (Java, Go, Swift) is a mechanical binding over the Rust engine rather than a redesign.
3.The shared WIT interface
WIT (WASM Interface Type) is the IDL we use to describe what every xlli engine must implement. The two engines and every binding speak through these types, so the parity story isn't aspirational — it's a contract that fails CI when broken.
// xlli.wit — the shared interface (excerpt)
package xlli:core@0.1.0;
interface engine {
/// Parse canonical xlli JSON into a typed model.
parse: func(json: string) -> result<document, parse-error>;
/// Serialise a model back to canonical xlli JSON,
/// byte-identical across implementations.
serialise: func(doc: document) -> string;
/// Canonicalise input that may be valid but non-canonical
/// (whitespace, key order). Returns canonical bytes.
canonicalise: func(json: string) -> result<string, parse-error>;
/// Validate a document against the v0.1 schema.
validate: func(doc: document) -> result<_, list<validation-error>>;
}
interface document {
resource document {
constructor();
xlli-version: func() -> string;
document-kind: func() -> string;
microtables: func() -> list<microtable>;
add-microtable: func(id: string, table: microtable);
}
}
world xlli-engine {
export engine;
export document;
}
The Rust engine implements this directly. The C# engine implements an equivalent interface generated from the same WIT source, so the surface area in both languages is provably the same. The JavaScript and Python bindings are thin generated wrappers — when WIT changes, the bindings regenerate.
WIT also makes adding a fourth or fifth language a mechanical exercise. Java via JNI, Go via cgo, Swift via WIT-direct bindings — same contract, mechanical port, no redesign.
4.What MicroTable looks like
A real example. Three products, Q1 revenue, a sparkline:
{
"xlliVersion": "0.1.0",
"documentKind": "container",
"meta": {
"id": "01HXM3V4F8Q2WK9C6YNBZPRS71",
"title": "Q1 Revenue Review",
"author": "Steve Dickson"
},
"microtables": {
"summary": {
"caption": "Q1 by Product",
"styles": {
"header": { "fontWeight": "bold", "backgroundColor": "#f5f5f5" },
"money": { "textAlign": "right", "fontVariantNumeric": "tabular-nums" }
},
"columns": [
{ "id": "product", "label": "Product", "styleName": "header" },
{ "id": "q1", "label": "Q1 Revenue", "styleName": "money" },
{ "id": "trend", "label": "Trend" }
],
"values": [
["Widget A", 42500, null],
["Widget B", 38100, null],
["Widget C", 29700, null]
],
"presentation": [
[null, null, null],
[null, null, null],
[null, null, { "type": "sparkline", "values": [10,12,15,14,18] }]
]
}
}
}
The data lives in matrices. values is a 3 × 3 grid. presentation is the same shape, with cell content overrides where a richer cell type is wanted. Styles are named once and referenced by string. Sparse overrides are available for tables where most positions are empty.
The shape is rectangular, the parsing is JSON, the rendering is one HTML table with one stylesheet. Implementations that go further — multi-sheet workbooks, the worksheets and workbooks dictionaries arriving in v0.2 — extend the same root container.
5.Roadmap
| Release | Timeline | Adds |
|---|---|---|
| v0.1 | ~2 months | microtables dictionary, canonical form, two reference engines (C# and Rust) plus JS and Python bindings, JSON Schema, HTML render |
| v0.2 | ~6 months | worksheets and workbooks dictionaries, EPPlus addin (Excel round-trip), 128-bit decimal encoding |
| v1.0 | ~12 months | Production-grade table engine, composition dictionaries (sections, cards, pages), full document story |
Public spec at xlli.org. Reference implementations open-source. Conformance test suite shared.
6.Get involved
xlli is a coordination format. It works best when more than one implementation exists and more than one ecosystem uses it. We are particularly interested in talking to:
- EPPlus users and contributors. EPPlus is the natural .NET distribution channel; v0.2 ships an addin that exposes
.xlliviaExcelPackageextension methods. - Browser-side spreadsheet authors. SheetJS, jspreadsheet, anyone building structured tables in the DOM. The
microtablesdictionary was designed to fit naturally into your data model. - Python data folks. The Rust+PyO3 binding gives you
.xllias a structured format that interoperates pleasantly with pandas and Polars without competing with them. - Excel power users and VBA legends. You know which 5% of features matter for 95% of real usage. We need the second opinion.
7.More
- Founder — Steve Dickson: lineage from Edinburgh 1990 through Lehman OPTMODEL to today
- Glossary: technical terms used across xlli.org, with cross-references to where each concept is discussed
- xlli architecture document — private draft, available on request
- Reference implementation repositories — TBA, opening with v0.1