Introducing XTJSON

A syntactic superset of JSON for files that are written and maintained by people.

Specification draft 0.3 · 15 November 2026 · Specification (English) · Spezifikation (Deutsch) · GitHub

XTJSON is a notation for JSON data that is comfortable to write by hand. It is meant for files that people edit, such as configuration files. It extends only the notation of JSON. The data model stays exactly that of JSON: object, array, string, number, true, false and null.

Every UTF-8 encoded JSON text is a valid XTJSON document with the same meaning, and every XTJSON document has a JSON equivalent that determines its value. XTJSON has no new data types, no expressions and no references. It is not an interchange format between machines; for that purpose JSON remains the format of choice.

XTJSON documents are encoded in UTF-8. The recommended file extensions are .xtjson and .xtj.

What XTJSON adds to JSON

  • Comments: // …, # … and /* … */.
  • Names without quotation marks: port: 8080.
  • Line breaks as separators: a line break can take the place of a comma.
  • Trailing commas in objects and arrays.
  • Optional braces around the object at the top level.
  • Strings in single quotation marks: '…'.
  • Multi-line strings with """ or ''', with the indentation removed.
  • More escape sequences: \', \v, \0, \xHH and \u{…}.
  • Hexadecimal, octal and binary integers: 0xFF, 0o755, 0b1010.
  • Digit grouping with _: 1_000_000.
  • Number suffixes: 10k, 64mb.
  • Named values: identifiers that the application defines as aliases for JSON values, such as nil.
  • More white space characters: all white space characters of ECMAScript.

Example

An XTJSON document and its JSON equivalent:

XTJSON
# Service configuration – the root braces are omitted
name: 'demo-service'
version: "1.4.0"
$schema: "https://example.org/schema/service.json"

listen: {
  host: "0.0.0.0"
  port: 8080            // the line break replaces the comma
  tls: false,           // commas remain permitted
}

/* Numbers in other bases
   and with digit grouping */
limits: {
  max_body_bytes: 10_485_760
  file_mode: 0o644
  feature_mask: 0b1010_0101
  magic: 0xDEAD_BEEF
  cache_bytes: 64mb     // predefined suffix: times 1048576
  rate_limit: 5k        // predefined suffix: times 1000
  "retry-after": 30     // a name with a hyphen needs quotation marks
  banner: 'Welcome to demo-service\x21'
}

// multi-line string: the common indentation is not part of the value
motd: """
  Welcome.
  Maintenance: Sundays 02:00.
  """

tags: [
  'internal'
  "says \"hello\""
  'says "hello"',
]
JSON equivalent
{
  "name": "demo-service",
  "version": "1.4.0",
  "$schema": "https://example.org/schema/service.json",
  "listen": { "host": "0.0.0.0", "port": 8080, "tls": false },
  "limits": {
    "max_body_bytes": 10485760,
    "file_mode": 420,
    "feature_mask": 165,
    "magic": 3735928559,
    "cache_bytes": 67108864,
    "rate_limit": 5000,
    "retry-after": 30,
    "banner": "Welcome to demo-service!"
  },
  "motd": "Welcome.\nMaintenance: Sundays 02:00.\n",
  "tags": ["internal", "says \"hello\"", "says \"hello\""]
}

The forms of XTJSON

The diagrams below show the notation. Boxes with round ends are characters to be written literally, rectangular boxes refer to another diagram, and boxes with a dashed border describe a set of characters. White space and comments may appear between any two tokens; the diagrams leave them out, except in the separator, where a line break matters.

document

A document is either a single value, as in JSON, or a sequence of members without enclosing braces. The second form denotes the object that contains exactly these members and needs at least one member. It exists only at the top level; an empty object is written as {}.

object

An object is an unordered set of name/value pairs. It begins with { and ends with }. If a name occurs more than once, the member that comes last in the document applies.

members

Each member is a name followed by : and a value. Members are separated by a separator. After the last member there may be exactly one trailing comma, which has no meaning.

array

An array is an ordered collection of values. It begins with [ and ends with ]. Values are separated by a separator; a single trailing comma is allowed.

separator

Two consecutive items are separated by exactly one comma, or by white space that contains at least one line break outside block comments. A comma together with line breaks is fine. Two items on the same line without a comma are an error, and so are two commas in a row.

, white space containing a line break

value

A value can be an object, an array, a string, a number or a named value. These structures can be nested.

named value

A named value is an identifier at a value position. It is another notation for the JSON value that a definition assigns to it. true, false and null are predefined and cannot be changed. The application can pass further definitions to the parser, for example nil for null. A document cannot define named values itself, case is significant, and an identifier without a definition is an error; it is never read as a string.

true false null identifier defined by the application

name

A name is a single-line string or an identifier. A multi-line string is not permitted as a name. The notation does not matter for the identity of a name: a, "a" and 'a' denote the same name. At a name position an identifier is always a name, so {null: null} has the name "null".

identifier

An identifier begins with an ASCII letter, _ or $, followed by any number of ASCII letters, ASCII digits, _ or $. Every other name must be written in quotation marks, for example names with spaces, hyphens, dots or non-ASCII characters.

a–z A–Z _ $ a–z A–Z 0–9 _ $

string

A string is a sequence of zero or more Unicode characters. A single-line string is enclosed in double or in single quotation marks; both forms are equivalent. The delimiter, the backslash and the control characters U+0000 to U+001F must be written as an escape, so a single-line string cannot extend across a line break.

" any character except " \ and control characters escape " ' any character except ' \ and control characters escape ' multi-line string

multi-line string

A multi-line string begins and ends with three double or three single quotation marks. Line breaks and tabs may appear in it unescaped. One or two delimiter characters in a row may also appear unescaped, but not immediately before the closing delimiter.

""" any character except \ and control characters line break tab escape """ ''' any character except \ and control characters line break tab escape '''

The value is obtained in four steps:

  1. The text is split into lines at every unescaped line break.
  2. Unescaped spaces and tabs at the end of every line except the last are removed.
  3. If the string begins with an empty line, that line is dropped and the smallest indentation of all non-empty lines is removed from every line. The last line always counts, so the position of the closing delimiter helps determine how much is removed. The removed characters must be the same in all lines; mixing tabs and spaces there is an error.
  4. The lines are joined with a line feed (U+000A), then the escape sequences are resolved.

escape

XTJSON has all escape sequences of JSON and five more from ECMAScript: \', \v, \0, \xHH and \u{…}. They apply in all string forms. \u{…} stands for a code point up to 10FFFF outside the surrogate range. Every other sequence that begins with a backslash is an error, including a backslash before a line break.

\ " quotation mark ' apostrophe \ backslash / slash b backspace f form feed n line feed r carriage return t tab v vertical tab 0 null; no digit may follow x 2 hex digits u 4 hex digits u { 1 or more hex digits, at most 10FFFF }

number

A number is a decimal number as in JSON or an integer in hexadecimal, octal or binary notation, optionally with a minus sign in front and a suffix directly behind. White space inside a number is an error.

decimal

Without _ this is exactly the number grammar of JSON: an integer part, an optional fraction part and an optional exponent. Leading zeros such as 0755, a leading + and a missing integer or fraction part (.5, 5.) remain errors.

0 1–9 _ digits . digits e E + - digits

digits

Between two digits there may be exactly one _. It has no meaning and is discarded when reading. This holds for every digit sequence, including the digits of hexadecimal, octal and binary integers.

0–9 _

integer

Integers can be written in hexadecimal (0x), octal (0o) or binary (0b) notation. Case does not matter in the prefix or in the hexadecimal digits. These forms have no fraction part and no exponent. Their value is exact: an implementation that cannot represent it reports an error and never rounds.

0 x X 0–9 a–f A–F _ o O 0–7 _ b B 0 1 _

suffix

A suffix of one or two characters directly after the last digit stands for a conversion function that turns the number into another number. Predefined and not changeable are k and K (times 1000), kb, Kb, kB and KB (times 1024), and mb, Mb, mB and MB (times 1048576). The application can pass further suffixes to the parser, for example % for "divided by 100". Case is significant, the longest match applies, and an undefined suffix is an error.

g–n p–w y z G–N P–W Y Z % $ a–z A–Z % $

The first character cannot be a to f, o or x in either case, so a suffix is never confused with a hexadecimal digit, an exponent or a number prefix.

white space

White space can be inserted between any pair of tokens. The white space characters are the four of JSON (space, tab, line feed, carriage return) and all further white space characters of ECMAScript, among them the no-break space and the byte order mark. A comment counts as white space.

white space character line break comment

comment

A line comment begins with // or # and extends up to the next line break or to the end of the document. A block comment begins with /* and ends at the first */ that follows; block comments cannot be nested. A line break inside a block comment is not a separator.

// # any character except LF and CR /* any text that does not contain */ */

line break

A line break is a line feed, a carriage return, or the sequence of both, which counts as a single line break. All other white space characters, including U+2028 and U+2029, are not line breaks: they neither separate items nor end a line comment.

LF (U+000A) CR (U+000D) CR LF (U+000D U+000A)

From XTJSON to JSON

The meaning of a document is defined by its JSON equivalent, which is obtained as follows:

  1. Comments are removed, and white space characters that JSON does not have become spaces.
  2. The members of an implicit root object are enclosed in { and }.
  3. Exactly one comma is put between two consecutive items; trailing commas are dropped.
  4. An identifier at a name position becomes the JSON string with the same characters.
  5. Strings become JSON strings in double quotation marks with the same value.
  6. Numbers are written as JSON numbers: without _, in decimal notation, and with the suffix replaced by the result of its conversion.
  7. Named values other than true, false and null are replaced by the JSON value assigned to them.

A document that uses at least one of the extensions is not a JSON text. It must not be labelled with the media type application/json or sent to recipients that expect JSON.

Principles

  • Definitions belong to the parser, not to the document. A document can define neither named values nor suffixes. A document that uses only the predefined ones is portable: every conforming parser can read it.
  • Errors instead of guessing. Unknown escape sequences, undefined identifiers, undefined suffixes and mixed indentation are errors and are not silently interpreted.
  • Never code. XTJSON is not a subset of a programming language and is never read by evaluating it as program code.

Specification and implementations

The specification is the source of truth. It is available in English and German; both versions are equal in rank. This page is an informal overview. Where it differs from the specification, the specification applies.

The reference parser in the repository is written in Python. It makes the specification testable and comes with the test cases that serve as the conformance corpus for implementations.