The JSON-stat Builder is a free, browser-based tool for creating JSON-stat
datasets interactively. Rather than hand-writing the verbose, error-prone
JSON-stat format by hand, you describe your data through a guided wizard and the
tool serializes a correct, validated dataset document for you — ready to copy
or download.
At a glance: design a cube → paste your data → export a valid JSON-stat 2.0
dataset. Click Load demo and you have a worked example (population by geography, sex and year) in seconds.
The builder's job is to take you from an idea — "I have a table of figures I
want to share as open statistical data" — to a finished JSON-stat document you
can publish, archive, or feed into the rest of the JSON-stat toolchain. You stay
focused on your content (what the numbers mean, how they are classified); the
tool takes care of the syntax (the exact shape a JSON-stat dataset must have
to be valid and interoperable). The output is plain JSON you fully own: dense
arrays or sparse objects, with optional observation status metadata, ready to
drop into any JSON-stat-aware consumer.
JSON-stat is powerful but not obvious: dimensions, categories, roles, dense vs.
sparse values, row-major ordering, the difference between what a field means
and where it sits in the document. Reading the specification helps, but there
is no substitute for building a cube yourself. The builder doubles as a learning
environment — every choice you make is reflected live in the generated output, so
you see, in real time, how a dimension becomes id + size, how categories
become category.index, and how your grid of numbers becomes the value array.
By the time you finish a dataset, you have not just produced a file; you
genuinely understand the format behind it.
A spreadsheet is a flat grid of rows and columns. JSON-stat is a cube: a
multi-dimensional structure where every observation sits at the crossing of one
category per dimension. Working with JSON-stat therefore starts with data
modelling, not data entry. The builder enforces this discipline gently: before
you type a single number, you name your dimensions (e.g. geography, sex,
year), give each an ordered list of categories (e.g. Spain, France,
Germany), and assign roles (time, geo, metric). This step is where
the meaning of your data is fixed, and it is what turns a loose table into a
well-defined statistical cube. The order you define dimensions in even governs
how values are stored — a detail the tool makes tangible instead of hidden.
Authoring a cube can feel daunting, so the builder breaks it into a clear, linear wizard of five steps:
- Metadata — give the dataset a
label, asource, anupdateddate, and an optionalhref: the descriptive context that makes the data citable. - Dimensions & categories — define each dimension and its categories, and assign roles. This is the modelling step described above.
- Layout — place each dimension on the row or column axis of a cross-tab grid. This is presentation only: it shapes what you see while entering data, never the document you ultimately export.
- Data — fill the grid with your numbers.
- Output — generate, validate, preview, copy, and download.
You can move back and forth freely, so refining an earlier choice (adding a category, relabelling a dimension) is always one click away.
Real-world data does not arrive in one neat shape, so the builder accepts yours
in several. You can enter information manually, one item at a time, or
import it in bulk from text — and the importer is deliberately tolerant,
auto-detecting whether you pasted tab-separated, semicolon-separated,
comma-separated, or whitespace-separated content (a nod to European locales,
where , is a decimal mark and ; a column separator). This applies both to
classifications (a dimension's long list of categories, e.g. every
municipality in a country, imported as id,label pairs) and to the data
itself.
The data you feed the grid can come straight from the tools you already use:
- Spreadsheet tables — copy a block of cells and paste it; the builder reads it into the grid shaped by your layout.
- CSV / TSV files — paste their contents directly.
- Tidy / long format — one observation per row (
dim1,dim2,…,value), the natural shape of most analytical exports. The builder maps each row to its coordinate, so it works regardless of how you laid out the grid. - Manual entry — type values directly into the grid for small datasets or quick edits.
An empty cell simply means a missing value (null), and you can annotate why
a value is missing with free-form observation status codes (estimated,
provisional, revised, …) — independently of the numbers.
Whatever path you take, the promise is the same: the document you export is
always a valid JSON-stat dataset. On every Generate, the output is checked
with the official JSON-stat tooling — vendored so it works fully offline —
which runs both structural (schema) and cross-field semantic checks, and then
reads the document back through the JSON-stat Toolkit to confirm every cell can
be reached. Copy and Download stay disabled until there are zero errors, so
you can never walk away with a broken file. The result is a dataset you can trust
to load anywhere in the JSON-stat ecosystem.
It is a static site with no build step. ES modules require an HTTP server:
python3 -m http.server 8000
# then open http://localhost:8000Click Load demo for a ready-made example (population by geography, sex and year).
The pure logic (model, serialize, parse, cats, tidy, validate) is unit-tested with
Node's built-in test runner:
npm testThe repo root is the source and runs with no build — python3 -m http.server
serves it directly for local testing. To produce the minified, bundled distribution
under /dist:
npm run build # -> /dist (index.html, css/style.css, js/app.js, js/vendor/, LICENSE)The app's own modules bundle into one minified dist/js/app.js; the
vendored libraries stay separate and lazy-loaded (copied verbatim into
dist/js/vendor/). Distribution files use plain names (app.js, style.css) —
never .min — and, because /dist mirrors the root's relative layout,
dist/index.html is an unchanged copy of the root index.html.
The build uses esbuild via the
esbuild-wasmpackage (pure WebAssembly — no native binary).
jsonstat-builder/
├── index.html # Single-page wizard
├── css/style.css # Custom (independent) styling
├── js/
│ ├── model.js # Cube model + row-major index math (pure)
│ ├── serialize.js # Builder state + cells -> JSON-stat dataset (pure)
│ ├── parse.js # textarea TSV/CSV auto-detect -> table (pure)
│ ├── cats.js # bulk id/label category import (pure)
│ ├── tidy.js # tidy/long -> dense value array (pure)
│ ├── validate.js # Cross-field semantic rules (pure; fallback + test target)
│ ├── vendor-validator.js # Official @jsonstat-validator/ts wrapper (lazy, fallback)
│ ├── toolkit.js # jsonstat-toolkit read-back (lazy)
│ ├── grid.js # Cross-tab grid renderer (DOM)
│ ├── fullscreen.js # Expand-to-fullscreen pane toggles (DOM)
│ ├── vendor/ # Vendored official libs (offline; see js/vendor/README.md)
│ └── app.js # Wizard orchestration (DOM)
├── test/ # node:test unit tests
├── samples/example.json # Example output
├── LICENSE # Apache 2.0
└── README.md
Output is verified by the official JSON-stat tooling, vendored for offline use
(no network at runtime, no build step). On Generate (js/app.js):
- Structural + semantic — the generated document is validated by the vendored
@jsonstat-validator/ts(js/vendor/jsonstat-validator.js, wrapped injs/vendor-validator.js). It runs the JSON Schema 2020-12 structural pass and the cross-field semantic rules (VALUE_LEN_MISMATCH,ID_SIZE_LEN_MISMATCH,INDEX_COUNT_MISMATCH,ROLE_ID_UNKNOWN,STATUS_*, …) in onevalidate()call. Findings are tagged by pass (structural/semantic) and engine. - Read-back — the document is read back through the vendored
jsonstat-toolkit(js/toolkit.js): it must parse, classify as adataset, and let every cell be reached (an independent check alongside the validator). - Gate — Copy / Download stay disabled until there are zero errors (warnings do not block).
The pure
js/validate.jsremains as the offline fallback (used only if the vendored module ever fails to load) and the Node unit-test target. The@jsonstat-validator/wasmsurface shares the identicalvalidate()signature and findings and is a documented drop-in upgrade; the TypeScript surface is preferred here because it is self-contained and needs no.wasmfetch (seejs/vendor/README.md).
Apache License, Version 2.0.