d3x0r/JSON6

JSON for Humans (ES6)

241

stars

190

commits

JavaScript

primary language

Sep 13, 2026

updated

javascript
json
json-parsing

README

JSON6 – JSON for Humans

Build Status

Join the chat at https://gitter.im/sack-vfs/json6

Documentation base cloned from JSON5 project https://github.com/json5/json5

JSON is an excellent data format, but can be better, and more expressive.

JSON6 is a proposed extension to JSON (Proposed here, noone, like em-discuss seemed to care about such a thing; prefering cryptic solutions like json-schema, or the 1000 pound gorilla solution). It aims to make it easier for humans to write and maintain by hand. It does this by adding some minimal syntax features directly from ECMAScript 6.

JSON6 is a superset of JavaScript, although adds no new data types, and works with all existing JSON content. Some features allowed in JSON6 are not directly supported by Javascript; although all javascript parsable features can be used in JSON6, except functions or any other code construct, transporting only data save as JSON.

JSON6 is not an official successor to JSON, and JSON6 content may not work with existing JSON parsers. For this reason, JSON6 files use a new .json6 extension. (TODO: new MIME type needed too.)

The code is a reference JavaScript implementation for both Node.js and all browsers. It is a completly new implementation.

Other related : JSOX JS Object Exchange format, which builds upon this and adds additional support for Date, BigNum, custom emissions, keyword-less class defintitions;default initializers, data condensation, flexible user exensibility.

Why

JSON isn’t the friendliest to write. Keys need to be quoted, objects and arrays can’t have trailing commas, and comments aren’t allowed — even though none of these are the case with regular JavaScript today.

That was fine when JSON’s goal was to be a great data format, but JSON’s usage has expanded beyond machines. JSON is now used for writing configs, manifests, even tests — all by humans.

There are other formats that are human-friendlier, like YAML, but changing from JSON to a completely different format is undesirable in many cases. JSON6’s aim is to remain close to JSON and JavaScript.

Features

The following is the exact list of additions to JSON’s syntax introduced by JSON6. All of these are optional, and MOST of these come from ES5/6.

Summary of Changes from JSON5

JSON6 includes all features of JSON5 plus the following.

  • Keyword undefined
  • Objects/Strings back-tick quoted strings (no template support, just uses same quote); Object key names can be unquoted.
  • Strings - generous multiline string definition; all javascript character escapes work. (\0, \x##, \u####, \u{} )
  • Numbers - underscore digit separation in numbers, octal 0o and binary 0b formats; all javascript number notations.
  • Arrays - empty members
  • Streaming reader interface
  • (Twice the speed of JSON5; subjective)
  • Opt-in esStrictCompatible mode that narrows the grammar to a subset of ECMAScript strict mode (see Why might you wish to enable ECMAScript compatibility?).

Objects

  • Object keys can be unquoted if they do not have ':', ']', '[', '{', '}', ',', any quote or whitespace; keywords will be interpreted as strings. Under the opt-in esStrictCompatible mode an unquoted key must additionally be a valid ECMAScript identifier name (or a number), so characters such as -, \, &, +, * and | then require quoting.

  • Object keys can be single-quoted, (JSON6) or back-tick quoted (the latter is rejected under esStrictCompatible); any valid string

  • Object keys can be double-quoted (original JSON).

  • Objects can have a single trailing comma. Excessive commas in objects will cause an exception. '{ a:123,,b:456 }' is invalid.

Arrays

  • Arrays can have trailing commas. If more than 1 is found, additional empty elements will be added.

  • (JSON6) Arrays can have comma ( ['test',,,'one'] ), which will result with empty values in the empty places.

Strings

  • Strings can be double-quoted (as per original JSON).

  • Strings can be single-quoted.

  • Strings can be back-tick (`) (grave accent) -quoted.

  • Strings can be split across multiple lines; just prefix each newline with a backslash. [ES5 §7.8.4]

  • (JSON6) all strings keep every character between the start and end, so single-, double-, and back-tick-quoted strings may all span multiple lines with the newlines preserved. If you do not want the newlines they can be escaped as previously mentioned. (Under the opt-in esStrictCompatible mode a literal, unescaped line terminator is rejected inside '/" strings — but still allowed inside back-ticks — so those strings stay valid ECMAScript.)

  • (JSON5+?) Strings can have characters emitted using 1 byte hex, interpreted as a utf8 codepoint \xNN, 2 and only 2 hex digits must follow \x; they may be 4 byte unicode characters \uUUUU, 4 and only 4 hex digits must follow \u; higher codepoints can be specified with \u{HHHHH}, (where H is a hex digit) This is permissive and may accept a single hex digit between { and }. All other standard escape sequeneces are also recognized. Any character that is not recognized as a valid escape character is emitted without the leading escape slash ( for example, "\012" will parse as "\0" + "12"). (Under the opt-in esStrictCompatible mode a legacy octal escape such as "\012" or "\1" is rejected.)

  • (JSON6) The interpretation of newline is dynamic treating \r, \n, and \r\n as valid combinations of line ending whitespace. The \ will behave approrpriately on those combinations. Mixed line endings like \n\r? or \n\r\n? are two line endings; 1 for newline, 1 for the \r(follwed by any character), and 1 for the newline, and 1 for the \r\n pair in the second case.

Numbers

  • (JSON6) Numbers can have underscores separating digits '_' these are treated as zero-width-non-breaking-space. (Proposal with the exception that _ can preceed or follow . and may be trailing.)

  • Numbers can be hexadecimal (base 16). ( 0x prefix )

  • (JSON6) Numbers can be binary (base 2). (0b prefix)

  • (JSON6) Numbers can be octal (base 8). (0o prefix)

  • (JSON6) Decimal Numbers can have leading zeros. (0 prefix followed by more numbers, without a decimal) 0123 is 123, not octal 83; see Leading 0 Octal. (Rejected under the opt-in esStrictCompatible mode, where ECMAScript treats it as legacy octal.)

  • Numbers can begin or end with a (leading or trailing) decimal point.

  • Numbers can include Infinity, -Infinity, NaN, and -NaN. (-NaN results as NaN)

  • Numbers can begin with an explicit plus sign.

  • (JSON6) Numbers can begin with multiple minus signs (for example '----123' === 123). (Two or more consecutive signs are rejected under the opt-in esStrictCompatible mode.)

Keyword Values

  • (JSON6) supports 'undefined' in addition to 'true', 'false', 'null'.

Comments

  • Both inline (single-line using '//' (todo:or '#'?) ) and block (multi-line using /* */ ) comments are allowed.
    • // comments end at a \r or \n character; They MAY also end at the end of a document (pass { warnWithCommentWithoutEOL: true } to log a warning when that happens).
    • /* comments should be closed before the end of a document or stream flush.
    • / followed by anything else other than / or * is an error.

Example

The following is a contrived example, but it illustrates most of the features:

{
	foo: 'bar',
	while: true,
	nothing : undefined, // why not?

	this: 'is a \
multi-line string',

	thisAlso: 'is a
multi-line string; but keeps newline',

	// this is an inline comment
	here: 'is another', // inline comment

	/* this is a block comment
	   that continues on another line */

	hex: 0xDEAD_beef,
	binary: 0b0110_1001,
	decimal: 123_456_789,
	octal: 0o123,
	half: .5,
	delta: +10,
	negative : ---123,
	to: Infinity,   // and beyond!

	finally: 'a trailing comma',
	oh: [
		"we shouldn't forget",
		'arrays can have',
		'trailing commas too',
	],
}

This implementation’s own package.JSON6 is more realistic:

// This file is written in JSON6 syntax, naturally, but npm needs a regular
// JSON file, so compile via `npm run build`. Be sure to keep both in sync!

{
	name: 'JSON6',
	version: '0.1.105',
	description: 'JSON for the ES6 era.',
	keywords: ['json', 'es6'],
	author: 'd3x0r <d3x0r@github.com>',
	contributors: [
		// TODO: Should we remove this section in favor of GitHub's list?
		// https://github.com/d3x0r/JSON6/contributors
	],
	main: 'lib/JSON6.js',
	bin: 'lib/cli.js',
	files: ["lib/"],
	dependencies: {},
	devDependencies: {
		gulp: "^3.9.1",
		'gulp-jshint': "^2.0.0",
		jshint: "^2.9.1",
		'jshint-stylish': "^2.1.0",
		mocha: "^2.4.5"
	},
	scripts: {
		build: 'node ./lib/cli.js -c package.JSON6',
		test: 'mocha --ui exports --reporter spec',
			// TODO: Would it be better to define these in a mocha.opts file?
	},
	homepage: 'http://github.com/d3x0r/JSON6/',
	license: 'MIT',
	repository: {
		type: 'git',
		url: 'https://github.com/d3x0r/JSON6',
	},
}

Community

Join the Google Group if you’re interested in JSON6 news, updates, and general discussion. Don’t worry, it’s very low-traffic.

The GitHub wiki (will be) a good place to track JSON6 support and usage. Contribute freely there!

GitHub Issues is the place to formally propose feature requests and report bugs. Questions and general feedback are better directed at the Google Group.

Usage

This JavaScript implementation of JSON6 simply provides a JSON6 object just like the native ES5 JSON object.

To use from Node:

npm install json-6
var JSON6 = require('json-6');

To use in the browser (adds the JSON6 object to the global namespace):

<script src="node_modules/json-6/lib/json6.js"></script>

Then in both cases, you can simply replace native JSON calls with JSON6:

var obj = JSON6.parse('{unquoted:"key",trailing:"comma",}');
var str = JSON6.stringify(obj); /* uses JSON stringify, so don't have to replace */
JSON6 MethodsparametersDescription
parse(string [,reviver] [,options])supports all of the JSON6 features listed above, as well as the native reviver argument. See Options for options.
stringify(value [,replacer] [,space] [,options])converts object to JSON. stringify. options.sortKeys (default true) can be set to false to keep an object's own key order instead of sorting it.
escape( string )substitutes ", , ', and ` with backslashed sequences. (prevent 'JSON injection')
begin(cb [,reviver] [,options] )create a JSON6 stream processor. cb is called with (value) for each value decoded from input given with write(). Optional reviver is called with each object before being passed to callback. options are the same as for parse and stay in effect across a bare reset().

JSON6.stringifier() returns a reusable stringifier object whose sortKeys (default true) and ignoreNonEnumerable (default false) properties can also be set directly:

const stringifier = JSON6.stringifier();
stringifier.sortKeys = false;
stringifier.stringify({ z: 1, a: 2 }); // '{z:1,a:2}' -- own key order kept

Options

parse, begin, and reset take an optional trailing options object. Every option is off by default, so out of the box JSON6 accepts its full historical grammar.

OptionDefaultEffect
esStrictCompatiblefalseWhen true, narrow the accepted grammar to a subset of ECMAScript strict mode, so anything that parses can be pasted into a module or eval'd unchanged (if trusted or using forbidTemplateSubstitution: true). Rejects: legacy-octal / leading-zero numbers (0123); legacy octal string escapes ("\012", "\1".."\9"); literal (unescaped) line terminators inside '/" strings — back-tick strings still allow them; back-tick-quoted object keys; unquoted object keys that are neither identifiers nor numbers ({a-b: 1}); and two or more consecutive unary +/- signs (--5).
forbidTemplateSubstitutionfalseWhen true, reject an unescaped ${ inside a back-tick-quoted string, so a document cannot silently turn into a template-literal substitution if it is later evaluated as JavaScript. Independent of esStrictCompatible.
warnWithCommentWithoutEOLfalseWhen true, a // comment that runs to the end of the document with no terminating line break logs a console warning. The input is accepted either way.

Strict mode is the yardstick because modules, classes, and any "use strict" code are always strict; constructs that are legal only in "sloppy" mode (the legacy octal escape "\1", or "\9" which sloppy mode silently reduces to "9") are still rejected by esStrictCompatible.

A numeric object key keeps its source text rather than JavaScript's normalized form, so {1e3:1} gives the key "1e3" here but "1000" in JavaScript (where a NumericLiteral key is converted through its numeric value). This applies under esStrictCompatible too: the "same meaning when pasted" guarantee is about what parses, not about the exact key string produced by each parser.

JSON6.parse("{'a-b': 0123}");                                        // { 'a-b': 123 }  (default)
JSON6.parse("{'a-b': 0123}", null, { esStrictCompatible: true });    // throws
JSON6.parse("`total: ${x}`");                                        // 'total: ${x}'  (default)
JSON6.parse("`total: ${x}`", null, { forbidTemplateSubstitution: true }); // throws

begin(cb, reviver, options) applies the options to the stream; a bare reset() keeps them, while reset(options) replaces them.

Why the default is not strict

The extensions exist because they make hand-written data less noisy, and for a data format — which is read and edited far more than it is pasted into code — that often matters more than JavaScript-parseability:

  • Leading zeros align columns. [ 001, 002, 010, 100 ] or a table of id: 0042 reads better than ragged values. Number() already treats "0123" as 123, so nothing is ambiguous; only a source-code lexer would see octal. (See Leading 0 Octal.)
  • Literal newlines in ordinary strings are the obvious behavior. Most people expect a quote to keep running until the closing quote; requiring a trailing \ on every line, or switching to back-ticks, is friction with no payoff when the file is never going to be executed.
  • Punctuation in unquoted keys. content-type, x-api-key, feature.flag and the like are extremely common config keys; quoting every one of them is the exact verbosity JSON6 set out to remove.
  • --5, +8, stray signs. Forgiving sign handling means machine-generated output and quick hand edits parse without complaint.
  • ${...} as plain text. In a data file there is no interpolation to worry about, so `price: ${amount}` is just a string; forbidding it only helps if the file will later be evaluated as code.

If a given file (or project) is meant to double as JavaScript, turn on esStrictCompatible and the parser will hold it to that stricter bar; otherwise the friendlier grammar is the point.

Why might you wish to enable ECMAScript compatibility?

JSON6 offers the potential for a .json6 document to also be a valid JavaScript value literal. Turning on esStrictCompatible keeps that true in every context, and that can buy a lot:

  • Paste-compatibility, both directions. You can copy a logged object (console.log(obj)) straight into a .json6 file, and paste a .json6 file into a .js module, eval it, or rename it to a JS config (module.exports = { … }) without edits or SyntaxErrors. A config authored in JSON6 can "graduate" to a real JS module the day it needs a computed value.
  • No silent changes of meaning. The dangerous cases aren't the ones that error — they're the ones that don't. 0123 is 123 in JSON6 but octal 83 in sloppy JS; `total: ${x}` is a literal string here but a substitution in JS; --5 is 5 here but decrement-nonsense elsewhere. An ES-compatible subset removes these by construction.
  • Existing tooling just works. Editors, syntax highlighters, bracket matchers, Prettier, ESLint, and AST libraries (acorn, Babel, TypeScript) already understand ES. If .json6 is an ES subset, all of that applies for free, and authors of new tools that touch .json6 are far less likely to get an edge case subtly wrong.
  • Familiarity, not a new dialect. Developers already know the rules; there is no "…but JSON6 also allows backtick keys and & in identifiers" to learn, and fewer typos that happen to parse as something unintended.
  • Interoperability instead of fragmentation. Other parsers in the ecosystem (e.g. eslint-plugin-jsonc, which already reads some JSON6) can be built on an ES grammar; every deviation is a place two implementations can disagree.
  • Forward compatibility. TC39 periodically assigns meaning to previously-free syntax. A grammar that stays inside today's ES is less likely to collide with tomorrow's. JSON itself learned this: the U+2028/U+2029 portability bug was fixed in ES2019 by making JSON a syntactic subset of ES.
  • It's the direction the standards went. The accepted tc39 JSON-superset proposal made the same argument from the other side.
  • Cheaper correctness. The parser can be checked against a reference: if V8/acorn accepts the text and produces value v, JSON6 should too. A narrower grammar is also less to audit.

JSON6 Streaming

A Parser that returns objects as they are encountered in a stream can be created. JSON.begin( dataCallback, reviver ); The callback is called for each complete object in a stream of data that is passed.

JSON6.begin( cb, reviver ) returns an object with a few methods.

MethodArgumentsDescription
write(string)Parse string passed and as objects are found, invoke the callback passed to begin() Objects are passed through optional reviver function passed to begin().
_write(string,completeAtEnd)Low level routine used internally. This does the work of parsing the passed string. Returns 0 if no object completed, 1 if there is no more data, and an object was completd, returns 2 if there is more data and a parsed object is found. if completedAtEnd is true, dangling values are returned, for example "1234" isn't known to be completed, more of the number might follow in another buffer; if completeAtEnd is passed, this iwll return as number 1234. Passing empty arguments steps to the next buffered input value.
value()Returns the currently completed object. Used to get the completed object after calling _write.
reset( [options] )If write() or \_write() throws an exception, no further objects will be parsed becuase internal status is false, this resets the internal status to allow continuing using the existing parser. ( May require some work to actually work for complex cases) With no argument the options passed to begin() stay in effect; pass an options object to change them.
	// This is (basically) the internal loop that write() uses.
	var result
	for( result = this._write(msg,false); result > 0; result = this._write() ) {
		var obj = this.value();
		// call reviver with (obj)
		// call callback with (obj)
	}
// Example code using write
function dataCallback( value ) {
	console.log( "Value from stream:", value );
}
var parser = JSON.begin( dataCallback );

parser.write( '"Hello ' );   // a broken simple value string, results as 'Hello World!'
parser.write( 'World!"' );
parser.write( '{ first: 1,' );   // a broken structure
parser.write( ' second : 2 }' );
parser.write( '[1234,12');  // a broken array across a value
parser.write( '34,1234]');
parser.write( '1234 456 789 123 523');  // multiple single simple values that are numbers
parser.write( '{a:1} {b:2} {c:3}');  // multiple objects

parser.write( '1234' );  // this won't return immediately, there might be more numeric data.
parser.write( '' ); // flush any pending numbers; if an object or array or string was split, throws an error; missing close.

parser.write( '1234' );
parser.write( '5678 ' );  // at this point, the space will flush the number value '12345678'

Extras

If you’re running this on Node, you can also register a JSON6 require() hook to let you require() .json6 files just like you can .json files:

require('JSON-6/lib/require');
require('./path/to/foo');   // tries foo.json6 after foo.js, foo.json, etc.
require('./path/to/bar.json6');

For ES modules, register the loader hooks before your program starts (Node 20.6 or newer):

node --import json-6/lib/register.mjs app.mjs
import config from './path/to/bar.json6';

On older Node versions the same hooks can be applied with --experimental-loader json-6/lib/import.mjs.

This module also provides a json6 executable (requires Node) for converting JSON6 files to JSON:

json6 -c path/to/foo.json6	# generates path/to/foo.json

Other Implementations

This is also implemented as part of npm [sack.vfs https://www.npmjs.com/package/sack.vfs] as a native code node.js addon. This native javascript version allows usage in browsers.

Benchmarks

This is as fast as the javascript version of Douglas Crockford's reference implementation JSON implementation for JSON parsing.

This is nearly double the speed of [JSON5 http://json5.org] implementation that inspired this (which is half the speed of Crockford's reference implementation).

This is half the speed of the sack.vfs native C++ node addon implementation (which itself is half the speed of V8's native code implementation, but they can cheat and build strings directly).

Requirements

Currently engines is set for Node 10 or higher.

However, let, const, and new unicode string support for codepoints (like codePointAt), are the most exotic of features used by the library.

Tests may include arrow functions.

For development purposes, this is tooled to always use the latest build tools, which require a minimum platform of their own.

External development dependencies

  • rollup - for packaging and minification
    • various rollup support plugins
  • eslint - for checking best practices and code styling
  • acorn - a peer dep. required through eslint
  • mocha (^3) - automated internal test suite
    • chai - enable expect syntax in tests
  • nyc - coverage testing; make sure there's a good reason for having things 😸
  • core-js - polyfill unicode string support

Development

git clone https://github.com/d3x0r/json6
cd json6
npm install
npm test

As the package.json6 file states, be sure to run npm run build on changes to package.json6, since npm requires package.json.

The parser source is src/json6.js; it is the only hand-edited file. npm run build generates lib/json6.js from it with the package version stamped in (build/src-to-lib.js), regenerates package.json, and then produces dist/ (rollup bundles and TypeScript declarations). Edits made directly in lib/ are overwritten on the next build. lib/json6.js is committed so that a checkout works without a build step; dist/ is not.

Feel free to file issues and submit pull requests — contributions are welcome. If you do submit a pull request, please be sure to add or update the tests, and ensure that npm test continues to pass.

Continuous Integration Testing

Travis CI is used to automatically test the package when pushed to github. Recently .mjs tests have been added, and rather than 1) build a switch to test mocha/test/*.js instead of just *, and 2) depending on node version switch the test command which is run, the older platforms were removed from testing.

The product of this should run on very old platforms also, especially node_modules/json-6/dist/index.min.js.

Leading 0 Octal

A number with a leading 0 followed by more digits is decimal. 0123 is 123. It is not octal (83), and it is not an error. (The opt-in esStrictCompatible mode is the exception: it rejects the token, matching ECMAScript strict mode.)

The rationale: JSON6 is a data format, not source code. The leading-zero-means-octal convention lives in source code lexers (C, C++, Java, Perl, Ruby, Go, shell, Python 2, sloppy-mode JavaScript). Every text-to-number conversion routine, which is what a parser of a data format is, reads a leading zero as just another decimal digit: Number("0123"), parseInt("0123", 10), parseFloat("0123"), Python's int("0123"), C's strtol(s, 0, 10) and strtod(), and Java's Integer.parseInt("0123") all give 123. None of them have ever produced 83.

So the rule is simple, and it is the same rule that already governs the other number forms: a numeric token means what Number() says it means. Number("0x1F") is 31, Number("0o17") is 15, Number("0b101") is 5, and Number("0123") is 123. Legacy octal is the only interpretation Number() has never accepted. The only places JSON6 departs from Number() are deliberate readability extensions: _ digit separators and a leading or trailing ..

The alternatives were considered and rejected:

  • Octal silently changes the value, disagrees with Number(), and nobody hand-writing a configuration file expects 0123 to be 83. Anyone who actually wants octal has 0o123.
  • Rejecting leading zeros is what JSON, JSON5, strict-mode JavaScript, Python 3, and Rust do. It loses nothing on the stringify side, since no serializer emits them, but it is strictly less friendly for hand-written files where zero padding is used for alignment, such as [ 001, 002, 010 ].

This is a deliberate divergence from ECMAScript strict mode, where 0123 is a syntax error. A .json6 document that uses leading zeros is therefore not valid JavaScript source; the same is true of JSON6's other extensions. JSOX takes the same stance. Callers who need the JavaScript-subset guarantee can turn on esStrictCompatible.

Changelog

  • 1.1.6(pre)
    • stringify emits arrays as arrays; they used to go through the object path and come out as {"0":..,"1":..}. Holes stay holes ([1,,3]), undefined stays undefined, and a trailing hole keeps its comma so it survives a round trip.
    • stringify always quotes string values (#57); only object keys use the bare-identifier rule.
    • Malformed input that used to be silently accepted now throws:
      • two values with no separator ([1 2] returned [2]),
      • an object field name with no value ({a} and {"a"} returned {}),
      • an incomplete keyword (tru returned undefined, [tru] returned []); a truncated keyword is still fine as an object key,
      • a sign with no number after it (- returned undefined),
      • a malformed number (1e, .5., 0x returned NaN),
      • a document with no value at all (empty, whitespace, or only comments) from parse(); parse("undefined") still returns undefined.
    • begin() no longer needs a callback; write() throws if there is none, since values can only leave it through the callback.
    • parse() restores its nested-parser level when it throws.
    • Vertical tab (U+000B), form feed (U+000C), line separator (U+2028) and paragraph separator (U+2029) are whitespace between tokens, as in ECMAScript; U+2028/U+2029 also end a // comment.
    • Remove no stringifier cavaet from documentation.
    • parse / begin / reset now take an options argument (#46). All options are off by default, so the historical grammar is unchanged.
      • esStrictCompatible restricts the grammar to a subset of ECMAScript strict mode: it rejects leading-zero / legacy-octal numbers, legacy octal string escapes ("\1".."\9"), literal line terminators inside '/" strings, backtick-quoted object keys, non-identifier unquoted keys, and two or more consecutive +/- signs.
      • forbidTemplateSubstitution rejects an unescaped ${ inside a backtick string.
      • warnWithCommentWithoutEOL restores the console warning for a // comment that ends the document without a line break (now silent by default).
    • stringify gains an opt-out sortKeys option (and a matching sortKeys property on JSON6.stringifier()); default true preserves the historical sorted-key output.
    • esStrictCompatible no longer rejects two inputs that are valid ES strict mode: a literal U+2028/U+2029 inside a '/" string (legal since ES2019), and an unquoted numeric key using a numeric separator ({1_000:1}).
  • 1.1.5
    • TypeScript declarations are generated from JSDoc during npm run build and shipped in dist/ (#55).
    • ESM loader for .json6 moved to the module.register() API; lib/register.mjs added.
    • Fix package exports: ESM entry pointed at a file that was never built; lib/require, lib/import.mjs, and lib/register.mjs subpaths were unreachable.
    • lib/json6.js can again be loaded directly with a <script> tag.
    • Parser source moved to src/json6.js; lib/json6.js is generated by npm run build with the package version stamped in, so JSON6.version can no longer drift from package.json.
    • Document the rationale for leading-zero numbers being decimal.
  • 1.1.4
    • fixes benchmark test for hex number conversion
  • 1.1.3
    • fixes '\v' decoding.
    • fixes parsing hex numbers with a-f.
  • 1.1.2
    • Updated document about CI tests.
    • added tests from sack.vfs JSON6 updates.
  • 1.1.1
    • Added stringifier
      • emits unquoted object field names, if valid to be unquoted.
      • emits Infinity
      • emits NaN
    • Added forgiving '+' collection for numbers.
    • Improved(implemented) node module loader interface lib/import.mjs which enables .json6 extension for import.
  • 1.0.8
    • throw error when streaming, and an error is encountered, persist throwing on new writes.
  • 1.0.7
    • Remove octal string escapes (Only overly clever people use those?)
    • Add \0 literal escape.
    • removed leading 0 octal interpretation.
    • fix trailing comma handling
    • clarify error reporting
    • Coverage completion
    • improve error tests
    • integrate with Travis.
  • 1.0.6
    • Remove leading 0 octal interpretation; code reformats, test framework improvements.
    • Implement automated mocha tests; fixed several edge cases
    • Comments that are open at the end of a document (stream flush), will throw an error; they should be closed with an end of line or */ as appropriate.
    • keywords are accepted as unquoted strings for object field names.
    • Improved error reporting for incomplete escape sequeneces at the end of strings.
  • 1.0.5 - Add interpretation of nbsp (codepoint 0xa0); (In the spirit of 'human readable') A 'visible' whitespace is treated as a whitespace.
  • 1.0.4 - error publishing (bump to republish)
  • 1.0.3
    • Fix clearing negative flag used with NaN.
    • update build products to produce an esm module.
  • 1.0.2 - Udate in Readme updated.
  • 1.0.1 - Fix homepage reference.
  • 1.0.0 - Fix bug reading surrogate pairs, and error with > 65k buffers. Release 1.0. I don't see this changing beyond the current functionality.
  • 0.1.127 - Fix bad shift/unshift/pop methods.
  • 0.1.126 - Fix handling very wide characters. Improved number parsing speed. Fix string character escapes. Update documentation to include '0o' prefix for numbers.
  • 0.1.125 - Fix some lets that were causing deoptimization
  • 0.1.123 - Fix npm install json-6 in readme. Remove dev dependancies that aren't used. Fix #8 Wierd arrays test
  • 0.1.122 - Fix referencing val.negative that should be just negative.
  • 0.1.121 - Optimization; use Number() instead of new Number()
  • 0.1.120 - If a non-string is passed to parse, convert to a string using String(msg).
  • 0.1.119 - standardize errors; fix negative sign for -Infinity.
  • 0.1.118 - Fix "use strict" undefined variables string_status and exponent_digit. Issue #4.
  • 0.1.117 - documentation and license updates. (Issue #3)
  • 0.1.116 - Updated docs; Fixed stream parse issue with numbers.
  • 0.1.115 - Fix object key names with spaces being accepted. Fix number parsing to be more strict.
  • 0.1.114 - Fix true/false values.
  • 0.1.113 - documentation update fix.
  • 0.1.112 - fix streaming error at end of string, and values in some circumstances.
  • 0.1.111 - fix packaging error.
  • 0.1.110 - fix empty elements in arrays. [,] = [<empty item>] not [undefined]. improve test.
  • 0.1.109 - fix redundant result with certain buffers.
  • 0.1.108 - rename 'add' to 'write' for compatibilty with other sack.vfs JSON6 parser.
  • 0.1.107 - fix variable used for gathering Strings that caused permanent error
  • 0.1.106 - fix handling whitespace after keyword
  • 0.1.105 - Add a streaming interface.
  • 0.1.104 - Readme updates.
  • 0.1.103 - Add underscore as a zero-space-non-breaking-whitespace for numbers.

License

MIT. See LICENSE.md for details.

Credits

(http://github.com/json5/json5) Inspring this project.

Contributors

d3x0r

107 commits

brettz9

81 commits

RichMorin

1 commits

d3x0r/JSON6

JSON for Humans (ES6)

241

stars

190

commits

JavaScript

primary language

Sep 13, 2026

updated

javascript
json
json-parsing

README

JSON6 – JSON for Humans

Build Status

Join the chat at https://gitter.im/sack-vfs/json6

Documentation base cloned from JSON5 project https://github.com/json5/json5

JSON is an excellent data format, but can be better, and more expressive.

JSON6 is a proposed extension to JSON (Proposed here, noone, like em-discuss seemed to care about such a thing; prefering cryptic solutions like json-schema, or the 1000 pound gorilla solution). It aims to make it easier for humans to write and maintain by hand. It does this by adding some minimal syntax features directly from ECMAScript 6.

JSON6 is a superset of JavaScript, although adds no new data types, and works with all existing JSON content. Some features allowed in JSON6 are not directly supported by Javascript; although all javascript parsable features can be used in JSON6, except functions or any other code construct, transporting only data save as JSON.

JSON6 is not an official successor to JSON, and JSON6 content may not work with existing JSON parsers. For this reason, JSON6 files use a new .json6 extension. (TODO: new MIME type needed too.)

The code is a reference JavaScript implementation for both Node.js and all browsers. It is a completly new implementation.

Other related : JSOX JS Object Exchange format, which builds upon this and adds additional support for Date, BigNum, custom emissions, keyword-less class defintitions;default initializers, data condensation, flexible user exensibility.

Why

JSON isn’t the friendliest to write. Keys need to be quoted, objects and arrays can’t have trailing commas, and comments aren’t allowed — even though none of these are the case with regular JavaScript today.

That was fine when JSON’s goal was to be a great data format, but JSON’s usage has expanded beyond machines. JSON is now used for writing configs, manifests, even tests — all by humans.

There are other formats that are human-friendlier, like YAML, but changing from JSON to a completely different format is undesirable in many cases. JSON6’s aim is to remain close to JSON and JavaScript.

Features

The following is the exact list of additions to JSON’s syntax introduced by JSON6. All of these are optional, and MOST of these come from ES5/6.

Summary of Changes from JSON5

JSON6 includes all features of JSON5 plus the following.

  • Keyword undefined
  • Objects/Strings back-tick quoted strings (no template support, just uses same quote); Object key names can be unquoted.
  • Strings - generous multiline string definition; all javascript character escapes work. (\0, \x##, \u####, \u{} )
  • Numbers - underscore digit separation in numbers, octal 0o and binary 0b formats; all javascript number notations.
  • Arrays - empty members
  • Streaming reader interface
  • (Twice the speed of JSON5; subjective)
  • Opt-in esStrictCompatible mode that narrows the grammar to a subset of ECMAScript strict mode (see Why might you wish to enable ECMAScript compatibility?).

Objects

  • Object keys can be unquoted if they do not have ':', ']', '[', '{', '}', ',', any quote or whitespace; keywords will be interpreted as strings. Under the opt-in esStrictCompatible mode an unquoted key must additionally be a valid ECMAScript identifier name (or a number), so characters such as -, \, &, +, * and | then require quoting.

  • Object keys can be single-quoted, (JSON6) or back-tick quoted (the latter is rejected under esStrictCompatible); any valid string

  • Object keys can be double-quoted (original JSON).

  • Objects can have a single trailing comma. Excessive commas in objects will cause an exception. '{ a:123,,b:456 }' is invalid.

Arrays

  • Arrays can have trailing commas. If more than 1 is found, additional empty elements will be added.

  • (JSON6) Arrays can have comma ( ['test',,,'one'] ), which will result with empty values in the empty places.

Strings

  • Strings can be double-quoted (as per original JSON).

  • Strings can be single-quoted.

  • Strings can be back-tick (`) (grave accent) -quoted.

  • Strings can be split across multiple lines; just prefix each newline with a backslash. [ES5 §7.8.4]

  • (JSON6) all strings keep every character between the start and end, so single-, double-, and back-tick-quoted strings may all span multiple lines with the newlines preserved. If you do not want the newlines they can be escaped as previously mentioned. (Under the opt-in esStrictCompatible mode a literal, unescaped line terminator is rejected inside '/" strings — but still allowed inside back-ticks — so those strings stay valid ECMAScript.)

  • (JSON5+?) Strings can have characters emitted using 1 byte hex, interpreted as a utf8 codepoint \xNN, 2 and only 2 hex digits must follow \x; they may be 4 byte unicode characters \uUUUU, 4 and only 4 hex digits must follow \u; higher codepoints can be specified with \u{HHHHH}, (where H is a hex digit) This is permissive and may accept a single hex digit between { and }. All other standard escape sequeneces are also recognized. Any character that is not recognized as a valid escape character is emitted without the leading escape slash ( for example, "\012" will parse as "\0" + "12"). (Under the opt-in esStrictCompatible mode a legacy octal escape such as "\012" or "\1" is rejected.)

  • (JSON6) The interpretation of newline is dynamic treating \r, \n, and \r\n as valid combinations of line ending whitespace. The \ will behave approrpriately on those combinations. Mixed line endings like \n\r? or \n\r\n? are two line endings; 1 for newline, 1 for the \r(follwed by any character), and 1 for the newline, and 1 for the \r\n pair in the second case.

Numbers

  • (JSON6) Numbers can have underscores separating digits '_' these are treated as zero-width-non-breaking-space. (Proposal with the exception that _ can preceed or follow . and may be trailing.)

  • Numbers can be hexadecimal (base 16). ( 0x prefix )

  • (JSON6) Numbers can be binary (base 2). (0b prefix)

  • (JSON6) Numbers can be octal (base 8). (0o prefix)

  • (JSON6) Decimal Numbers can have leading zeros. (0 prefix followed by more numbers, without a decimal) 0123 is 123, not octal 83; see Leading 0 Octal. (Rejected under the opt-in esStrictCompatible mode, where ECMAScript treats it as legacy octal.)

  • Numbers can begin or end with a (leading or trailing) decimal point.

  • Numbers can include Infinity, -Infinity, NaN, and -NaN. (-NaN results as NaN)

  • Numbers can begin with an explicit plus sign.

  • (JSON6) Numbers can begin with multiple minus signs (for example '----123' === 123). (Two or more consecutive signs are rejected under the opt-in esStrictCompatible mode.)

Keyword Values

  • (JSON6) supports 'undefined' in addition to 'true', 'false', 'null'.

Comments

  • Both inline (single-line using '//' (todo:or '#'?) ) and block (multi-line using /* */ ) comments are allowed.
    • // comments end at a \r or \n character; They MAY also end at the end of a document (pass { warnWithCommentWithoutEOL: true } to log a warning when that happens).
    • /* comments should be closed before the end of a document or stream flush.
    • / followed by anything else other than / or * is an error.

Example

The following is a contrived example, but it illustrates most of the features:

{
	foo: 'bar',
	while: true,
	nothing : undefined, // why not?

	this: 'is a \
multi-line string',

	thisAlso: 'is a
multi-line string; but keeps newline',

	// this is an inline comment
	here: 'is another', // inline comment

	/* this is a block comment
	   that continues on another line */

	hex: 0xDEAD_beef,
	binary: 0b0110_1001,
	decimal: 123_456_789,
	octal: 0o123,
	half: .5,
	delta: +10,
	negative : ---123,
	to: Infinity,   // and beyond!

	finally: 'a trailing comma',
	oh: [
		"we shouldn't forget",
		'arrays can have',
		'trailing commas too',
	],
}

This implementation’s own package.JSON6 is more realistic:

// This file is written in JSON6 syntax, naturally, but npm needs a regular
// JSON file, so compile via `npm run build`. Be sure to keep both in sync!

{
	name: 'JSON6',
	version: '0.1.105',
	description: 'JSON for the ES6 era.',
	keywords: ['json', 'es6'],
	author: 'd3x0r <d3x0r@github.com>',
	contributors: [
		// TODO: Should we remove this section in favor of GitHub's list?
		// https://github.com/d3x0r/JSON6/contributors
	],
	main: 'lib/JSON6.js',
	bin: 'lib/cli.js',
	files: ["lib/"],
	dependencies: {},
	devDependencies: {
		gulp: "^3.9.1",
		'gulp-jshint': "^2.0.0",
		jshint: "^2.9.1",
		'jshint-stylish': "^2.1.0",
		mocha: "^2.4.5"
	},
	scripts: {
		build: 'node ./lib/cli.js -c package.JSON6',
		test: 'mocha --ui exports --reporter spec',
			// TODO: Would it be better to define these in a mocha.opts file?
	},
	homepage: 'http://github.com/d3x0r/JSON6/',
	license: 'MIT',
	repository: {
		type: 'git',
		url: 'https://github.com/d3x0r/JSON6',
	},
}

Community

Join the Google Group if you’re interested in JSON6 news, updates, and general discussion. Don’t worry, it’s very low-traffic.

The GitHub wiki (will be) a good place to track JSON6 support and usage. Contribute freely there!

GitHub Issues is the place to formally propose feature requests and report bugs. Questions and general feedback are better directed at the Google Group.

Usage

This JavaScript implementation of JSON6 simply provides a JSON6 object just like the native ES5 JSON object.

To use from Node:

npm install json-6
var JSON6 = require('json-6');

To use in the browser (adds the JSON6 object to the global namespace):

<script src="node_modules/json-6/lib/json6.js"></script>

Then in both cases, you can simply replace native JSON calls with JSON6:

var obj = JSON6.parse('{unquoted:"key",trailing:"comma",}');
var str = JSON6.stringify(obj); /* uses JSON stringify, so don't have to replace */
JSON6 MethodsparametersDescription
parse(string [,reviver] [,options])supports all of the JSON6 features listed above, as well as the native reviver argument. See Options for options.
stringify(value [,replacer] [,space] [,options])converts object to JSON. stringify. options.sortKeys (default true) can be set to false to keep an object's own key order instead of sorting it.
escape( string )substitutes ", , ', and ` with backslashed sequences. (prevent 'JSON injection')
begin(cb [,reviver] [,options] )create a JSON6 stream processor. cb is called with (value) for each value decoded from input given with write(). Optional reviver is called with each object before being passed to callback. options are the same as for parse and stay in effect across a bare reset().

JSON6.stringifier() returns a reusable stringifier object whose sortKeys (default true) and ignoreNonEnumerable (default false) properties can also be set directly:

const stringifier = JSON6.stringifier();
stringifier.sortKeys = false;
stringifier.stringify({ z: 1, a: 2 }); // '{z:1,a:2}' -- own key order kept

Options

parse, begin, and reset take an optional trailing options object. Every option is off by default, so out of the box JSON6 accepts its full historical grammar.

OptionDefaultEffect
esStrictCompatiblefalseWhen true, narrow the accepted grammar to a subset of ECMAScript strict mode, so anything that parses can be pasted into a module or eval'd unchanged (if trusted or using forbidTemplateSubstitution: true). Rejects: legacy-octal / leading-zero numbers (0123); legacy octal string escapes ("\012", "\1".."\9"); literal (unescaped) line terminators inside '/" strings — back-tick strings still allow them; back-tick-quoted object keys; unquoted object keys that are neither identifiers nor numbers ({a-b: 1}); and two or more consecutive unary +/- signs (--5).
forbidTemplateSubstitutionfalseWhen true, reject an unescaped ${ inside a back-tick-quoted string, so a document cannot silently turn into a template-literal substitution if it is later evaluated as JavaScript. Independent of esStrictCompatible.
warnWithCommentWithoutEOLfalseWhen true, a // comment that runs to the end of the document with no terminating line break logs a console warning. The input is accepted either way.

Strict mode is the yardstick because modules, classes, and any "use strict" code are always strict; constructs that are legal only in "sloppy" mode (the legacy octal escape "\1", or "\9" which sloppy mode silently reduces to "9") are still rejected by esStrictCompatible.

A numeric object key keeps its source text rather than JavaScript's normalized form, so {1e3:1} gives the key "1e3" here but "1000" in JavaScript (where a NumericLiteral key is converted through its numeric value). This applies under esStrictCompatible too: the "same meaning when pasted" guarantee is about what parses, not about the exact key string produced by each parser.

JSON6.parse("{'a-b': 0123}");                                        // { 'a-b': 123 }  (default)
JSON6.parse("{'a-b': 0123}", null, { esStrictCompatible: true });    // throws
JSON6.parse("`total: ${x}`");                                        // 'total: ${x}'  (default)
JSON6.parse("`total: ${x}`", null, { forbidTemplateSubstitution: true }); // throws

begin(cb, reviver, options) applies the options to the stream; a bare reset() keeps them, while reset(options) replaces them.

Why the default is not strict

The extensions exist because they make hand-written data less noisy, and for a data format — which is read and edited far more than it is pasted into code — that often matters more than JavaScript-parseability:

  • Leading zeros align columns. [ 001, 002, 010, 100 ] or a table of id: 0042 reads better than ragged values. Number() already treats "0123" as 123, so nothing is ambiguous; only a source-code lexer would see octal. (See Leading 0 Octal.)
  • Literal newlines in ordinary strings are the obvious behavior. Most people expect a quote to keep running until the closing quote; requiring a trailing \ on every line, or switching to back-ticks, is friction with no payoff when the file is never going to be executed.
  • Punctuation in unquoted keys. content-type, x-api-key, feature.flag and the like are extremely common config keys; quoting every one of them is the exact verbosity JSON6 set out to remove.
  • --5, +8, stray signs. Forgiving sign handling means machine-generated output and quick hand edits parse without complaint.
  • ${...} as plain text. In a data file there is no interpolation to worry about, so `price: ${amount}` is just a string; forbidding it only helps if the file will later be evaluated as code.

If a given file (or project) is meant to double as JavaScript, turn on esStrictCompatible and the parser will hold it to that stricter bar; otherwise the friendlier grammar is the point.

Why might you wish to enable ECMAScript compatibility?

JSON6 offers the potential for a .json6 document to also be a valid JavaScript value literal. Turning on esStrictCompatible keeps that true in every context, and that can buy a lot:

  • Paste-compatibility, both directions. You can copy a logged object (console.log(obj)) straight into a .json6 file, and paste a .json6 file into a .js module, eval it, or rename it to a JS config (module.exports = { … }) without edits or SyntaxErrors. A config authored in JSON6 can "graduate" to a real JS module the day it needs a computed value.
  • No silent changes of meaning. The dangerous cases aren't the ones that error — they're the ones that don't. 0123 is 123 in JSON6 but octal 83 in sloppy JS; `total: ${x}` is a literal string here but a substitution in JS; --5 is 5 here but decrement-nonsense elsewhere. An ES-compatible subset removes these by construction.
  • Existing tooling just works. Editors, syntax highlighters, bracket matchers, Prettier, ESLint, and AST libraries (acorn, Babel, TypeScript) already understand ES. If .json6 is an ES subset, all of that applies for free, and authors of new tools that touch .json6 are far less likely to get an edge case subtly wrong.
  • Familiarity, not a new dialect. Developers already know the rules; there is no "…but JSON6 also allows backtick keys and & in identifiers" to learn, and fewer typos that happen to parse as something unintended.
  • Interoperability instead of fragmentation. Other parsers in the ecosystem (e.g. eslint-plugin-jsonc, which already reads some JSON6) can be built on an ES grammar; every deviation is a place two implementations can disagree.
  • Forward compatibility. TC39 periodically assigns meaning to previously-free syntax. A grammar that stays inside today's ES is less likely to collide with tomorrow's. JSON itself learned this: the U+2028/U+2029 portability bug was fixed in ES2019 by making JSON a syntactic subset of ES.
  • It's the direction the standards went. The accepted tc39 JSON-superset proposal made the same argument from the other side.
  • Cheaper correctness. The parser can be checked against a reference: if V8/acorn accepts the text and produces value v, JSON6 should too. A narrower grammar is also less to audit.

JSON6 Streaming

A Parser that returns objects as they are encountered in a stream can be created. JSON.begin( dataCallback, reviver ); The callback is called for each complete object in a stream of data that is passed.

JSON6.begin( cb, reviver ) returns an object with a few methods.

MethodArgumentsDescription
write(string)Parse string passed and as objects are found, invoke the callback passed to begin() Objects are passed through optional reviver function passed to begin().
_write(string,completeAtEnd)Low level routine used internally. This does the work of parsing the passed string. Returns 0 if no object completed, 1 if there is no more data, and an object was completd, returns 2 if there is more data and a parsed object is found. if completedAtEnd is true, dangling values are returned, for example "1234" isn't known to be completed, more of the number might follow in another buffer; if completeAtEnd is passed, this iwll return as number 1234. Passing empty arguments steps to the next buffered input value.
value()Returns the currently completed object. Used to get the completed object after calling _write.
reset( [options] )If write() or \_write() throws an exception, no further objects will be parsed becuase internal status is false, this resets the internal status to allow continuing using the existing parser. ( May require some work to actually work for complex cases) With no argument the options passed to begin() stay in effect; pass an options object to change them.
	// This is (basically) the internal loop that write() uses.
	var result
	for( result = this._write(msg,false); result > 0; result = this._write() ) {
		var obj = this.value();
		// call reviver with (obj)
		// call callback with (obj)
	}
// Example code using write
function dataCallback( value ) {
	console.log( "Value from stream:", value );
}
var parser = JSON.begin( dataCallback );

parser.write( '"Hello ' );   // a broken simple value string, results as 'Hello World!'
parser.write( 'World!"' );
parser.write( '{ first: 1,' );   // a broken structure
parser.write( ' second : 2 }' );
parser.write( '[1234,12');  // a broken array across a value
parser.write( '34,1234]');
parser.write( '1234 456 789 123 523');  // multiple single simple values that are numbers
parser.write( '{a:1} {b:2} {c:3}');  // multiple objects

parser.write( '1234' );  // this won't return immediately, there might be more numeric data.
parser.write( '' ); // flush any pending numbers; if an object or array or string was split, throws an error; missing close.

parser.write( '1234' );
parser.write( '5678 ' );  // at this point, the space will flush the number value '12345678'

Extras

If you’re running this on Node, you can also register a JSON6 require() hook to let you require() .json6 files just like you can .json files:

require('JSON-6/lib/require');
require('./path/to/foo');   // tries foo.json6 after foo.js, foo.json, etc.
require('./path/to/bar.json6');

For ES modules, register the loader hooks before your program starts (Node 20.6 or newer):

node --import json-6/lib/register.mjs app.mjs
import config from './path/to/bar.json6';

On older Node versions the same hooks can be applied with --experimental-loader json-6/lib/import.mjs.

This module also provides a json6 executable (requires Node) for converting JSON6 files to JSON:

json6 -c path/to/foo.json6	# generates path/to/foo.json

Other Implementations

This is also implemented as part of npm [sack.vfs https://www.npmjs.com/package/sack.vfs] as a native code node.js addon. This native javascript version allows usage in browsers.

Benchmarks

This is as fast as the javascript version of Douglas Crockford's reference implementation JSON implementation for JSON parsing.

This is nearly double the speed of [JSON5 http://json5.org] implementation that inspired this (which is half the speed of Crockford's reference implementation).

This is half the speed of the sack.vfs native C++ node addon implementation (which itself is half the speed of V8's native code implementation, but they can cheat and build strings directly).

Requirements

Currently engines is set for Node 10 or higher.

However, let, const, and new unicode string support for codepoints (like codePointAt), are the most exotic of features used by the library.

Tests may include arrow functions.

For development purposes, this is tooled to always use the latest build tools, which require a minimum platform of their own.

External development dependencies

  • rollup - for packaging and minification
    • various rollup support plugins
  • eslint - for checking best practices and code styling
  • acorn - a peer dep. required through eslint
  • mocha (^3) - automated internal test suite
    • chai - enable expect syntax in tests
  • nyc - coverage testing; make sure there's a good reason for having things 😸
  • core-js - polyfill unicode string support

Development

git clone https://github.com/d3x0r/json6
cd json6
npm install
npm test

As the package.json6 file states, be sure to run npm run build on changes to package.json6, since npm requires package.json.

The parser source is src/json6.js; it is the only hand-edited file. npm run build generates lib/json6.js from it with the package version stamped in (build/src-to-lib.js), regenerates package.json, and then produces dist/ (rollup bundles and TypeScript declarations). Edits made directly in lib/ are overwritten on the next build. lib/json6.js is committed so that a checkout works without a build step; dist/ is not.

Feel free to file issues and submit pull requests — contributions are welcome. If you do submit a pull request, please be sure to add or update the tests, and ensure that npm test continues to pass.

Continuous Integration Testing

Travis CI is used to automatically test the package when pushed to github. Recently .mjs tests have been added, and rather than 1) build a switch to test mocha/test/*.js instead of just *, and 2) depending on node version switch the test command which is run, the older platforms were removed from testing.

The product of this should run on very old platforms also, especially node_modules/json-6/dist/index.min.js.

Leading 0 Octal

A number with a leading 0 followed by more digits is decimal. 0123 is 123. It is not octal (83), and it is not an error. (The opt-in esStrictCompatible mode is the exception: it rejects the token, matching ECMAScript strict mode.)

The rationale: JSON6 is a data format, not source code. The leading-zero-means-octal convention lives in source code lexers (C, C++, Java, Perl, Ruby, Go, shell, Python 2, sloppy-mode JavaScript). Every text-to-number conversion routine, which is what a parser of a data format is, reads a leading zero as just another decimal digit: Number("0123"), parseInt("0123", 10), parseFloat("0123"), Python's int("0123"), C's strtol(s, 0, 10) and strtod(), and Java's Integer.parseInt("0123") all give 123. None of them have ever produced 83.

So the rule is simple, and it is the same rule that already governs the other number forms: a numeric token means what Number() says it means. Number("0x1F") is 31, Number("0o17") is 15, Number("0b101") is 5, and Number("0123") is 123. Legacy octal is the only interpretation Number() has never accepted. The only places JSON6 departs from Number() are deliberate readability extensions: _ digit separators and a leading or trailing ..

The alternatives were considered and rejected:

  • Octal silently changes the value, disagrees with Number(), and nobody hand-writing a configuration file expects 0123 to be 83. Anyone who actually wants octal has 0o123.
  • Rejecting leading zeros is what JSON, JSON5, strict-mode JavaScript, Python 3, and Rust do. It loses nothing on the stringify side, since no serializer emits them, but it is strictly less friendly for hand-written files where zero padding is used for alignment, such as [ 001, 002, 010 ].

This is a deliberate divergence from ECMAScript strict mode, where 0123 is a syntax error. A .json6 document that uses leading zeros is therefore not valid JavaScript source; the same is true of JSON6's other extensions. JSOX takes the same stance. Callers who need the JavaScript-subset guarantee can turn on esStrictCompatible.

Changelog

  • 1.1.6(pre)
    • stringify emits arrays as arrays; they used to go through the object path and come out as {"0":..,"1":..}. Holes stay holes ([1,,3]), undefined stays undefined, and a trailing hole keeps its comma so it survives a round trip.
    • stringify always quotes string values (#57); only object keys use the bare-identifier rule.
    • Malformed input that used to be silently accepted now throws:
      • two values with no separator ([1 2] returned [2]),
      • an object field name with no value ({a} and {"a"} returned {}),
      • an incomplete keyword (tru returned undefined, [tru] returned []); a truncated keyword is still fine as an object key,
      • a sign with no number after it (- returned undefined),
      • a malformed number (1e, .5., 0x returned NaN),
      • a document with no value at all (empty, whitespace, or only comments) from parse(); parse("undefined") still returns undefined.
    • begin() no longer needs a callback; write() throws if there is none, since values can only leave it through the callback.
    • parse() restores its nested-parser level when it throws.
    • Vertical tab (U+000B), form feed (U+000C), line separator (U+2028) and paragraph separator (U+2029) are whitespace between tokens, as in ECMAScript; U+2028/U+2029 also end a // comment.
    • Remove no stringifier cavaet from documentation.
    • parse / begin / reset now take an options argument (#46). All options are off by default, so the historical grammar is unchanged.
      • esStrictCompatible restricts the grammar to a subset of ECMAScript strict mode: it rejects leading-zero / legacy-octal numbers, legacy octal string escapes ("\1".."\9"), literal line terminators inside '/" strings, backtick-quoted object keys, non-identifier unquoted keys, and two or more consecutive +/- signs.
      • forbidTemplateSubstitution rejects an unescaped ${ inside a backtick string.
      • warnWithCommentWithoutEOL restores the console warning for a // comment that ends the document without a line break (now silent by default).
    • stringify gains an opt-out sortKeys option (and a matching sortKeys property on JSON6.stringifier()); default true preserves the historical sorted-key output.
    • esStrictCompatible no longer rejects two inputs that are valid ES strict mode: a literal U+2028/U+2029 inside a '/" string (legal since ES2019), and an unquoted numeric key using a numeric separator ({1_000:1}).
  • 1.1.5
    • TypeScript declarations are generated from JSDoc during npm run build and shipped in dist/ (#55).
    • ESM loader for .json6 moved to the module.register() API; lib/register.mjs added.
    • Fix package exports: ESM entry pointed at a file that was never built; lib/require, lib/import.mjs, and lib/register.mjs subpaths were unreachable.
    • lib/json6.js can again be loaded directly with a <script> tag.
    • Parser source moved to src/json6.js; lib/json6.js is generated by npm run build with the package version stamped in, so JSON6.version can no longer drift from package.json.
    • Document the rationale for leading-zero numbers being decimal.
  • 1.1.4
    • fixes benchmark test for hex number conversion
  • 1.1.3
    • fixes '\v' decoding.
    • fixes parsing hex numbers with a-f.
  • 1.1.2
    • Updated document about CI tests.
    • added tests from sack.vfs JSON6 updates.
  • 1.1.1
    • Added stringifier
      • emits unquoted object field names, if valid to be unquoted.
      • emits Infinity
      • emits NaN
    • Added forgiving '+' collection for numbers.
    • Improved(implemented) node module loader interface lib/import.mjs which enables .json6 extension for import.
  • 1.0.8
    • throw error when streaming, and an error is encountered, persist throwing on new writes.
  • 1.0.7
    • Remove octal string escapes (Only overly clever people use those?)
    • Add \0 literal escape.
    • removed leading 0 octal interpretation.
    • fix trailing comma handling
    • clarify error reporting
    • Coverage completion
    • improve error tests
    • integrate with Travis.
  • 1.0.6
    • Remove leading 0 octal interpretation; code reformats, test framework improvements.
    • Implement automated mocha tests; fixed several edge cases
    • Comments that are open at the end of a document (stream flush), will throw an error; they should be closed with an end of line or */ as appropriate.
    • keywords are accepted as unquoted strings for object field names.
    • Improved error reporting for incomplete escape sequeneces at the end of strings.
  • 1.0.5 - Add interpretation of nbsp (codepoint 0xa0); (In the spirit of 'human readable') A 'visible' whitespace is treated as a whitespace.
  • 1.0.4 - error publishing (bump to republish)
  • 1.0.3
    • Fix clearing negative flag used with NaN.
    • update build products to produce an esm module.
  • 1.0.2 - Udate in Readme updated.
  • 1.0.1 - Fix homepage reference.
  • 1.0.0 - Fix bug reading surrogate pairs, and error with > 65k buffers. Release 1.0. I don't see this changing beyond the current functionality.
  • 0.1.127 - Fix bad shift/unshift/pop methods.
  • 0.1.126 - Fix handling very wide characters. Improved number parsing speed. Fix string character escapes. Update documentation to include '0o' prefix for numbers.
  • 0.1.125 - Fix some lets that were causing deoptimization
  • 0.1.123 - Fix npm install json-6 in readme. Remove dev dependancies that aren't used. Fix #8 Wierd arrays test
  • 0.1.122 - Fix referencing val.negative that should be just negative.
  • 0.1.121 - Optimization; use Number() instead of new Number()
  • 0.1.120 - If a non-string is passed to parse, convert to a string using String(msg).
  • 0.1.119 - standardize errors; fix negative sign for -Infinity.
  • 0.1.118 - Fix "use strict" undefined variables string_status and exponent_digit. Issue #4.
  • 0.1.117 - documentation and license updates. (Issue #3)
  • 0.1.116 - Updated docs; Fixed stream parse issue with numbers.
  • 0.1.115 - Fix object key names with spaces being accepted. Fix number parsing to be more strict.
  • 0.1.114 - Fix true/false values.
  • 0.1.113 - documentation update fix.
  • 0.1.112 - fix streaming error at end of string, and values in some circumstances.
  • 0.1.111 - fix packaging error.
  • 0.1.110 - fix empty elements in arrays. [,] = [<empty item>] not [undefined]. improve test.
  • 0.1.109 - fix redundant result with certain buffers.
  • 0.1.108 - rename 'add' to 'write' for compatibilty with other sack.vfs JSON6 parser.
  • 0.1.107 - fix variable used for gathering Strings that caused permanent error
  • 0.1.106 - fix handling whitespace after keyword
  • 0.1.105 - Add a streaming interface.
  • 0.1.104 - Readme updates.
  • 0.1.103 - Add underscore as a zero-space-non-breaking-whitespace for numbers.

License

MIT. See LICENSE.md for details.

Credits

(http://github.com/json5/json5) Inspring this project.

Contributors

d3x0r

107 commits

brettz9

81 commits

RichMorin

1 commits

Languages

JavaScript

100.0%