terset
Color theme

Spec, version 1

The Terset format

One table for a set of records. Every rule is here. The raw file is at /spec.md.

Terset format, version 1

TERse SETs. Terset writes a JSON object or array as short lines. Any list of two or more objects becomes one tab-separated table when that is smaller than compact JSON.

Data

The encoder accepts a root JSON object or array. Its values are plain objects, dense arrays, strings, booleans, null and finite numbers. Undefined, cycles, symbol keys, non-plain objects and depth above 500 are errors. Numeric -0 is an explicit error. Text "-0" round-trips. An empty root object is {}. Key order is JavaScript own enumerable key order, including the integer-key ordering JSON uses.

Lines and cells

Each root field is key: value, or key: followed by a table header and rows. A root array is a table with no name line, or compact JSON. Lines use LF. There is no added final LF. An absent final one-column cell is an empty row, so that text ends with LF. Simple keys match [A-Za-z_$][A-Za-z0-9_$.-]*. Other keys are JSON strings.

A table has one tab-separated header, with any first field. Each row has exactly that many tab-separated cells, including trailing empty cells. An empty cell means the field is absent. Empty text is "". Null is null. Numbers, booleans and null keep their types in every ordinary column.

A leading bare id header marks string ids. Its cells are bare strings, even 1, true or 007. This needs every row to start with a nonempty string id with no edge whitespace and no troublesome characters. For any other first id column the header is written "id", which selects ordinary typed cells. An id column that is not first uses ordinary cells.

Other strings are written bare unless they are empty, have edge whitespace, start with a quote, bracket or brace, contain control characters, DEL, U+0085, U+2028 or U+2029, or look numeric. Numeric-looking includes 007, +5, 1e3, .5 and -0. The words true, false, null, undefined, NaN, Infinity and -Infinity are quoted as text. Other scalars and nested values are compact JSON. Tabs and LF inside text use JSON escapes. Lone surrogate code units are JSON-escaped, so UTF-8 transport cannot replace them. Astral pairs are kept as they are.

No legend is written into the data. The package exports one rules line, RULES, to put once in the system prompt, outside the data:

> Tables use tab-separated headers and rows. Empty cells mean absent. JSON quotes distinguish numeric, boolean or null text from scalars. Nested cells use JSON. A leading bare id header marks string IDs.

Table selection and fallback

An array of two or more objects becomes a table when the table is strictly shorter than the array's compact JSON in UTF-8 bytes. The header is built in row order by appending first-seen fields. Each row's keys must follow that order. Fields may be absent, the first field included. Empty object rows are allowed when the list has at least one field. No id is required. Numeric ids, booleans, null and mixed-type columns are supported.

There is one fallback. The whole list stays compact JSON if it has fewer than two rows, a row that is not an object, no fields, a key order that does not fit the header, or a table that is not smaller. Inside a root object this is a key: JSON line. At the root it is the JSON array itself.

Tables are chosen only at the root or for direct fields of a root object. Nested lists and objects stay compact JSON cells. Terset does no sorting, no column reordering, no constant hoisting and no nested shaping.

One-column strings that look like a root key: line are JSON-quoted. If an id looks like such a line, the header uses the typed "id" mode instead. Root field lines hold no raw tabs. Rows with many columns are recognized by the exact header width. One-column tables end at a root field line. This keeps boundaries clear with no counts, row markers or indentation.

Decoder

The decoder accepts only the encoder's canonical output. It parses JSON with JSON.parse and never evaluates text. It rejects duplicate keys and extra cells. It re-encodes its result to reject noncanonical input, duplicate nested keys included. Properties are defined as own data, so __proto__ and constructor cannot change an object's prototype. The depth limit also applies to JSON cells. The same input always gives byte-identical output.

Limits

Numeric negative zero is rejected. Depth above 500 is rejected. Text "-0", lone surrogates, 100 kB strings and 5,000 rows round-trip. Large finite numbers keep their IEEE-754 value. Precision lost before encoding cannot come back. NFC and NFD text stay different code units, in values and in keys. There is no other Unicode or size limit.