JSON6 JSON6 Numbers Reference

Decimal, hexadecimal, octal and binary literals, underscore separators, leading zeros, signs, Infinity, NaN and precision limits.

TL;DR: JSON6 numbers accept 0x, 0o and 0b prefixes, _ separators, leading zeros (0123 is 123, never octal), any run of + and - signs, .5 and 5., Infinity and NaN. Every number is an IEEE 754 double, so integers above 2^53 lose precision and 1e400 silently becomes Infinity.

Number forms at a glance

JSON allows one number form: an optional minus sign, integer digits with no leading zero, an optional fraction and an optional exponent. JSON5 relaxes that by allowing hexadecimal, a leading plus, a leading or trailing decimal point, Infinity and NaN. JSON6 keeps all of JSON5 and adds binary, octal, underscore separators, leading zeros, repeated signs and signs separated by spaces. The README states the organizing idea: "a numeric token means what Number() says it means," with deliberate departures only for underscores and a leading or trailing dot.

FormExampleValueJSONJSON5JSON6
Decimal integer4242YesYesYes
Fraction and exponent-1.5e3-1500YesYesYes
Leading decimal point.50.5NoYesYes
Trailing decimal point5.5NoYesYes
Explicit plus+1010NoYesYes
Hexadecimal0xFF255NoYesYes
Infinity and NaN-Infinity-InfinityNoYesYes
Binary0b1015NoNoYes
Octal0o1715NoNoYes
Digit separators1_000_0001000000NoNoYes
Leading zeros0123123NoNoYes
Multiple signs---123-123NoNoYes

The rest of this page gives the exact rules and the edge cases found by running each form through json-6 1.1.7.

Decimal numbers

A decimal literal is digits, an optional fraction, and an optional exponent introduced by e or E with an optional sign. At least one digit must appear in the integer or fraction part. A decimal point on its own is not a number.

InputResultNote
1.51.5
.50.5Leading dot is allowed.
5.5Trailing dot is allowed.
-.5-0.5
.throws "Invalid number"A dot needs a digit on at least one side.
1e31000Both e and E work.
1e+31000
1e-30.001
1.e31000Trailing dot before an exponent is allowed.
.5e15
1ethrows "Invalid number"An exponent needs digits (changed in 1.1.6; it used to give NaN).
1.2.3throwsA second dot is an error.
1e3.5throwsExponents are integers.

Negative zero is preserved: -0 parses to the JavaScript value -0, which prints as 0 but is distinguishable with Object.is. JSON6.stringify writes -0 as 0, so the sign does not survive a round trip.

Hexadecimal, octal and binary

Three prefixes switch the base. Each prefix may be written in upper or lower case, and hexadecimal digits may be in either case. A prefix with no digits after it is an error, and a digit outside the base is an error. None of the three accepts a fraction or an exponent.

BasePrefixesDigitsExampleValueNew in JSON6?
Hexadecimal0x 0X0-9 a-f A-F0xDEAD_beef3735928559No (JSON5)
Octal0o 0O0-70o1715Yes
Binary0b 0B0-10b1010_0101165Yes
InputResult
0x1016
-0x10-16 (signs may precede the prefix)
+0x1016
0xDEADbeef3735928559 (mixed case)
0xthrows "Invalid number"
0xgthrows "fault while parsing number"
0b2throws "Invalid number"
0o8throws "Invalid number"
0x1.8throws (no fractions in hex)
0b1.1throws (no fractions in binary)
0o1.1throws (no fractions in octal)

The octal prefix is the letter o, not a bare leading zero. Write 0o755 for the Unix permission mask 493. Writing 0755 gives the decimal number 755, which is almost certainly not what you meant. See the next section.

Binary and octal literals are useful in hardware, protocol and permission tables, where a hex or bit pattern is the natural way to say a value. A register map such as { ctrl: 0b1000_0001, mask: 0xFF00, mode: 0o644 } reads cleanly and parses without a build step.

Underscore digit separators

An underscore between digits is ignored. The README says underscores are "treated as zero-width-non-breaking-space," following the ECMAScript numeric separator proposal "with the exception that underscore can precede or follow the decimal point, and may be trailing." In practice the parser is far more permissive than the proposal, which only allows a single underscore strictly between two digits.

InputResultES numeric separator?
1_0001000Valid in ECMAScript
123_456.789_012123456.789012Valid
0xdead_beef3735928559Valid
0b1_02Valid
0o1_715Valid
1__010Not valid in ECMAScript (consecutive)
1_1Not valid (trailing)
1_.51.5Not valid (before dot)
1._51.5Not valid (after dot)
1_e31000Not valid (before exponent)
1e_31000Not valid (after e)
0x_11Not valid (after prefix)
0_11Not valid
_1throwsA leading underscore starts no number

The practical rule: an underscore cannot be the first character of a number, but may appear almost anywhere after the first digit or prefix. Do not rely on the permissive cases. A file that uses 1__0 or 1_ is valid JSON6 but would fail in JavaScript, so write underscores only as single separators between digits, in groups of three for decimals or four for hexadecimal and binary.

Underscores are accepted in unquoted numeric object keys as well, and esStrictCompatible does not reject them in either place; the option explicitly allows {1_000: 1} since 1.1.6. Line and column numbers in error messages count the underscore characters correctly as of 1.1.6.

Leading zeros are decimal

This is the most surprising rule if you come from C, Java, Go or sloppy-mode JavaScript, and the README devotes a whole section to defending it. A number written with a leading zero followed by more digits is decimal. It is not octal and it is not an error.

InputResultSloppy-mode JavaScript
012312383 (legacy octal)
00777
0000
0888 (not valid octal, decimal fallback)
0999
-0123-123-83
0123.5123.5syntax error
0123e11230syntax error
00.50.5syntax error

The README's reasoning is that the leading-zero-is-octal convention belongs to source code lexers, while every text-to-number routine in every language (Number("0123"), parseInt("0123", 10), Python's int("0123"), C's strtol) reads it as 123. It also notes the practical benefit: tables such as [ 001, 002, 010, 100 ] line up in columns. Anyone who wants octal has 0o.

A JSON6 file with a leading-zero number is not valid JavaScript, and the same digits mean something else in other tools. Zip codes, product codes and phone numbers written as 02134 become the number 2134 and lose their zero. Quote identifiers as strings. With esStrictCompatible: true, 0123 throws "Legacy octal / leading-zero numbers are not allowed".

Signs

JSON allows a minus sign. JSON5 adds a plus sign. JSON6 allows any number of signs in front of any numeric literal, including a hexadecimal, octal or binary literal, Infinity and NaN. Each minus flips the sign, and each plus does nothing.

InputResultInputResult
+11-+1-1
--11++11
---1-1+-1-1
- 1-1--InfinityInfinity
-throws "Sign with no number following"+throws "Sign with no number following"

Whitespace between the sign and the digits is accepted, so - 1 is -1. That is not documented in the README, which mentions only multiple minus signs such as '----123' === 123. It is the sort of looseness that can hide a stray character, so esStrictCompatible rejects two or more consecutive signs. A single sign is still allowed, and so is a single sign followed by a space such as - 1; only repeated signs trigger the check.

Infinity and NaN

Infinity and NaN are JSON5 inheritances. They are written exactly with that capitalization; infinity, INFINITY and nan throw, and Inf throws "Incomplete keyword". Signs work: -Infinity, +Infinity, -NaN (which is NaN) and +NaN all parse. The README notes explicitly that "-NaN results as NaN."

These values cannot be represented in JSON. JSON.stringify(NaN) gives null, which silently changes the data. JSON6.stringify writes NaN, Infinity and -Infinity as themselves so a JSON6 round trip preserves them, but a JSON consumer cannot read the output.

As an unquoted object key, Infinity and NaN are strings like any other bare word, so {Infinity: 1, NaN: 2} has the keys "Infinity" and "NaN".

Precision and range

JSON6 adds no numeric types. Every number token becomes a JavaScript Number, an IEEE 754 binary64 double, and there is no BigInt support (that is a JSOX feature). The consequences are the same as for plain JavaScript, plus a few that come from the extra literal forms.

InputResultWhy
90071992547409939007199254740992Above 2^53 integers are not all representable.
1234567890123456789012345678901234567000Digits beyond about 17 are lost.
0x7fffffffffffffff9223372036854776000A 64-bit hex id is rounded.
0b followed by sixty 1 digits1152921504606847000Binary also rounds past 53 bits.
1e400InfinityOverflow is silent, not an error.
-1e400-Infinity
1e-4000Underflow is silent.
5e-3245e-324The smallest positive double.
0.300000000000000040.30000000000000004Doubles print the shortest round-trip form.

Do not store 64-bit identifiers, nanosecond timestamps or exact money amounts as bare JSON6 numbers. Quote them as strings and convert with BigInt in your code. This is true of JSON too, but JSON6's hex and binary forms make it easy to write a 64-bit constant without noticing that it will be rounded.

Numbers as object keys

An unquoted object key that looks like a number is still a string key, and by default it keeps its source text. {1e3: true} gives the key "1e3", {0x10: 1} gives "0x10" and {1_000: 1} gives "1_000". JavaScript would convert a numeric literal key through its numeric value, giving "1000" for the first. The normalizeNumericKeys option closes that gap by replacing the key with String(Number(key)).

Key as writtenDefaultnormalizeNumericKeys: true
1e3"1e3""1000"
0x10"0x10""16"
.5".5""0.5"
1_0"1_0""10"
0123"0123""123"
1.0"1.0""1"
+1"+1""1" (collides with a key written 1)
-1"-1""-1"
Infinity"Infinity""Infinity"

The README warns that Number("0123") is 123 while a JavaScript literal 0123 in sloppy mode is 83, so combine the option with esStrictCompatible if the file also needs to be valid JavaScript. More about key rules is on the objects and arrays page.

FAQ

Is 0123 octal in JSON6?

No. A leading zero followed by digits is decimal, so 0123 is 123. Octal needs the 0o prefix: 0o123 is 83. With esStrictCompatible the leading-zero form throws.

Can I use underscores in JSON6 numbers?

Yes, as in 1_000_000 or 0xDEAD_beef. They are ignored. The parser accepts more positions than JavaScript does, so stick to single underscores between digits to keep the file valid ECMAScript.

Does JSON6 support BigInt?

No. All numbers are IEEE 754 doubles, so integers above 9007199254740991 can lose precision. BigInt values are a feature of JSOX, not JSON6. Quote large integers as strings.

What happens to a number that is too large?

1e400 parses to Infinity without any error, and 1e-400 parses to 0. If you need to detect overflow, check Number.isFinite on the result.

Why does - 1 parse as -1?

The parser skips whitespace between a sign and the digits. It is undocumented, and esStrictCompatible does not reject it, so avoid it in hand-written files.

Check your own numbers

Paste hex, binary and separator literals into the validator to see the parsed values.