A project of the Open Tech Foundation.
Caution
EXPERIMENTAL PRE-RELEASE
STF is in an experimental, pre-release state, for research and feedback only. The specification and the reference implementations may change incompatibly at any time.
The 1.0 draft specification is complete and all four reference implementations pass the conformance corpus — see Conformance Status.
STF (Structured Text Format) is a human-readable, structured data format for configuration and data interchange. It is designed to replace JSON, JSONC, JSON5, NDJSON, and JSONL for new work — not to be a superset of them. STF deliberately does not inherit their looseness, so some valid JSON has no STF representation; conversion fails loudly rather than guessing.
# A configuration file
{
service: `checkout-api`,
port: 8080,
enabled: T,
deploy_after: TIMESTAMP(2026-01-15T10:30:00Z),
price_cap: DECIMAL(199.00), # exact — scale is preserved
account_id: BIGINT(9007199254740993),
signing_key: BINARY(SGVsbG8=),
regions: [`eu-west-1`, `us-east-1`],
}
JSON forces every non-primitive type through strings, so a reader cannot tell a date from a
string that looks like one, and 19.99 is never exactly 19.99. STF makes the type explicit at
the point of use, with no schema or convention required:
| JSON | STF |
|---|---|
"created": "2026-01-15" — date, or string? |
created: DATE(2026-01-15) |
"id": "9007199254740993" — string to survive JSON.parse |
id: BIGINT(9007199254740993) |
"price": 19.99 — binary64, not exact |
price: DECIMAL(19.99) |
"blob": "SGVsbG8=" — base64 by convention |
blob: BINARY(SGVsbG8=) |
- Unquoted keys —
[A-Za-z0-9_-]+, no quotes, no ambiguity. - Two string forms —
`raw`preserves everything literally;"interpreted"supports JSON escapes. - One-character literals —
T,F,Nfor true, false, null. - Explicit typed constructors —
BIGINT,DECIMAL,DATE,TIMESTAMP,BINARY. - Scale-sensitive decimals —
DECIMAL(1.5)≠DECIMAL(1.50). Usable for money. - Comments —
#to end of line. - Trailing commas — permitted in objects and arrays.
- Canonical form — one byte encoding per value, for hashing and signing.
- Normative error codes — every rejection maps to exactly one documented code.
- Record streams —
.stfsfor append-only event logs.
Normative
- STF 1.0 Specification — the format
- Standardized Error Codes — condition → code, exact
- STF Stream Profile —
.stfsline-delimited record streams - STF Schema Specification — optional validation layer
Non-normative
stfCommand-Line Tool — check, format, lint, canonicalize, convert- Migration Guide — from JSON, JSON5, NDJSON, DTXT, and pre-1.0 drafts
- Comparison with Other Formats
The conformance corpus is the executable contract: 258 cases, each traced
to a normative rule. Runners must compare error codes exactly and check value kinds, so
a string can never satisfy a DECIMAL expectation.
./scripts/check_conformance.sh| Implementation | Corpus | Notes |
|---|---|---|
| Rust | 258/258 | The reference implementation; also ships the stf CLI |
| JavaScript | 258/258 | |
| Python | 258/258 | |
| Go | 258/258 |
All four implement the same eleven-kind data model, exact error codes, Canonical Form, and the
stream profile, and all four verify parse(serialize(parse(input))) on every value case.
cargo install --path ref-impl/rust
stf check config.stf # verify, with normative error codes
stf fmt --write config.stf # format in place
stf lint config.stf # flag stringly-typed values
stf canon config.stf | sha256sum # canonical form, for hashing and signing
stf convert data.json --to stf # refuses what STF cannot represent
stf lsp # language server, for editorsFull reference: doc/cli.md.
stf lsp serves the Language Server Protocol over stdio, so any LSP-capable editor gets
diagnostics and formatting from the reference parser itself — the same normative error codes
stf check reports in CI, and the same warnings as stf lint, never a second approximate
implementation. .stf documents and .stfs streams are both served.
Configuration is per editor; see doc/cli.md §8. VS Code
has no built-in client for a new language, so it uses the extension in
vscode-stf/, which launches the same server.
The marketing site, documentation, and playground live in website/, built with
OTF Web. bun install && bun run dev.
All four are conformant with STF 1.0.
- Rust — the reference; also the
stfCLI - JavaScript / TypeScript
- Python
- Go
Payload size: STF is 18.3% smaller than JSON on the same data, from unquoted keys and one-character literals. Every implementation now generates the same fixed-seed dataset and measures against a minified JSON baseline, so this figure is reproducible.
Parse speed depends far more on the implementation than on the format:
| STF | Native JSON | ||
|---|---|---|---|
| Rust | 70 ms | 113 ms (serde_json) |
STF ~38% faster |
| Go | 73 ms | 95 ms (encoding/json) |
STF ~23% faster |
| JS (Node) | 164 ms | 33 ms (JSON.parse) |
native parser is C++ |
| Python | 1837 ms | 79 ms (json.loads) |
pure-Python scanner vs C |
The honest summary: STF's grammar is cheap to parse, but a reference implementation written in the host language does not beat a native JSON parser written in C. The Rust and Go rows compare two parsers in the same language and are the meaningful ones; the JS and Python rows compare an STF parser in the language against a JSON parser in C, and are not a property of the format.
Figures are within-language only — each implementation benchmarks its own dataset on its own machine. Full numbers: benchmarks/RESULTS.md.
Dedicated to the public domain under the CC0 1.0 Universal Public Domain Dedication.