JSON6 Strings and Escapes
Three quote styles, multi-line text, back-ticks without templates, every escape sequence and the rules for unknown escapes.
TL;DR: JSON6 strings may use double quotes, single quotes or back-ticks, and all three keep literal line breaks. Back-tick strings never substitute ${...}. \u{...} reaches any code point up to U+10FFFF. An unknown escape such as \q silently becomes the plain character, and \012 is NUL followed by the text 12.
Quote styles
JSON allows one kind of string: double-quoted. JSON5 adds single quotes. JSON6 adds the back-tick (the grave accent, U+0060). All three produce exactly the same kind of value, an ordinary JavaScript string, and all three can be used for object keys as well as values.
[ "double quoted", 'single quoted', `back-tick quoted`, ]Inside any of the three, the other two quote characters need no escape. The only quote that has to be escaped is the one that closes the string. Using the escape for a quote you did not need to escape is harmless.
| JSON6 text | Resulting string |
|---|---|
"it's" | it's |
'say "hi"' | say "hi" |
`both ' and "` | both ' and " |
'it\'s' | it's |
"say \"hi\"" | say "hi" |
"a\`b" | a`b |
An opening quote with no matching close throws Incomplete string, whichever style you used. A closing quote must match the opening one, so "abc' is incomplete rather than a string that happens to end in an apostrophe. Two strings next to each other with no comma between them, such as "a" "b" at the top level, are not concatenated: the parser returns the first and ignores the rest. Inside an array that same text throws.
Single-quoted strings were already in JSON5, so any JSON5 string literal parses unchanged. Back-ticks are the new one. If you plan to move a file back to JSON5 or to JSON, see the differences page for the conversion rules.
Back-tick strings and template substitution
A back-tick string looks like a JavaScript template literal but it is not one. JSON6 treats the back-tick purely as a quote character and "just uses same quote," in the README's words. There is no interpolation, no tagged templates and no evaluation. The text ${name} inside back-ticks is four plain characters followed by a brace, kept exactly as written.
JSON6.parse('`total: ${price}`') // 'total: ${price}' JSON6.parse('`cost: $5`') // 'cost: $5' JSON6.parse('`a \${b}`') // 'a ${b}' (escaped, still literal)The danger the README identifies is not in JSON6 itself but in what happens next. If you later paste the same text into JavaScript, or evaluate it, a back-tick string with ${...} becomes a real substitution that can run code. The forbidTemplateSubstitution option exists to catch that early: with it set, an unescaped dollar sign followed by an opening brace inside a back-tick string throws.
| Input | Default | forbidTemplateSubstitution: true |
|---|---|---|
`a${x}b` | a${x}b | throws "Template substitution ${ } is not allowed" |
`a\${x}b` | a${x}b | a${x}b (escaped dollar is allowed) |
`a$b` | a$b | a$b |
"a${x}" | a${x} | a${x} (only back-ticks are checked) |
'a${x}' | a${x} | a${x} (only back-ticks are checked) |
The check applies only to back-tick strings, as verified on 1.1.7. A ${x} inside a double-quoted or single-quoted string is accepted even with the option on, because those quote styles are not template literals in JavaScript. See the FAQ for other safety questions.
The option is independent of esStrictCompatible. Turn it on whenever a file could ever be evaluated as code, and leave it off for plain data files where ${ is just text.
Multi-line strings
JSON strings cannot contain a raw line break, and JSON5 allows a line break only after a backslash. JSON6 does both. By default every string, in every quote style, may contain literal line terminators, and those line terminators are kept in the value. A backslash immediately before a line terminator removes it instead.
{ kept: 'first line second line', // value contains a newline joined: 'first \ second', // value is 'first second' tick: `also kept`, }| Source | Value | Note |
|---|---|---|
"a<LF>b" | a<LF>b | Literal LF is kept. |
"a<CR><LF>b" | a<CR><LF>b | CRLF is kept as two characters; nothing is normalized. |
"a\<LF>b" | ab | Backslash plus LF is a continuation. |
"a\<CR><LF>b" | ab | Backslash plus CRLF is one continuation. |
"a\<U+2028>b" | ab | LS and PS also continue. |
"a<U+2028>b" | LS kept | A raw U+2028 is legal inside a string. |
"a\<LF><CR>b" | a<CR>b | LF then CR is two line ends; only the first is consumed. |
One bug was found while verifying. A backslash followed by a lone carriage return (no line feed) and then a letter is mis-parsed: the letter after the CR is treated as an escape. "a\<CR>bc" yields a, backspace, c instead of abc, and 'a\<CR>n' yields a newline. Files with classic Mac line endings (CR only) should be converted to LF before parsing. LF and CRLF files are unaffected.
The behavior is the README's "all strings keep every character between the start and end." The rationale given is that most people expect a quote to keep running until the closing quote, and requiring a backslash on every line is friction with no payoff for data that is never evaluated. When a file is going to be evaluated as JavaScript, esStrictCompatible rejects a raw line terminator inside a single- or double-quoted string, because JavaScript would too, but still allows it inside back-ticks. A backslash continuation remains legal under the option.
Escape sequences
Escapes work in all three quote styles. The table covers every escape the parser treats specially, tested one by one against json-6 1.1.7.
| Escape | Produces | Rule |
|---|---|---|
\n | Line feed U+000A | |
\r | Carriage return U+000D | |
\t | Tab U+0009 | |
\b | Backspace U+0008 | |
\f | Form feed U+000C | |
\v | Vertical tab U+000B | Not in JSON. Fixed in 1.1.3. |
\0 | NUL U+0000 | A following digit is a separate character, see below. |
\xNN | Code point 0 to 255 | Exactly two hex digits. Fewer throws "Incomplete hexidecimal sequence". |
\uNNNN | UTF-16 code unit | Exactly four hex digits. Fewer throws "Incomplete unicode sequence". |
\u{H...} | Any code point up to U+10FFFF | See the next section. |
\\ | Backslash | |
\/ | Slash | Accepted for JSON compatibility. |
\" \' \` | The quote character | Any quote can be escaped in any style. |
\<line end> | Nothing | Line continuation. |
\q \a \$ \z | The plain character | Unknown escapes drop the backslash. |
Hex and unicode escapes
\x41 is A and \u0041 is A. Escapes are case-insensitive for hex digits. The fixed width is strict: "\x4" and "\u004" both throw. A pair of \uD83D\uDE00 escapes combines into one emoji, exactly as in JavaScript. A lone high or low surrogate such as "\ud83d" is accepted and produces an unpaired surrogate in the string, which is not valid Unicode text and will not encode to UTF-8 cleanly. Avoid it.
Unknown escapes and legacy octal
JSON throws on an unknown escape. JSON5 also throws on digits after a backslash. JSON6 does neither: "any character that is not recognized as a valid escape character is emitted without the leading escape slash." The consequences, all verified:
"\q"isq,"\a"isaand"\$"is$."\1"is the character1,"\8"is8and"\9"is9. This is not legacy octal."\012"is NUL followed by the two characters 12, three characters in all. The README documents this example: it parses as"\0" + "12". In sloppy-mode JavaScript the same text is a newline.- A typo like
"C:\new\table"does not throw. It gives a newline and a tab. A Windows path written with single backslashes is silently corrupted, so double every backslash.
Under esStrictCompatible, legacy octal escapes are rejected: "\012", "\1" to "\9" all throw, while "\0" alone and ordinary unknown escapes such as "\q" are still allowed, because strict-mode JavaScript allows them.
The long unicode escape u{...}
\u{...} is the ECMAScript 2015 code point escape and it is one of JSON6's headline additions. The braces hold any number of hex digits, and the result is the code point itself, which becomes a surrogate pair in the JavaScript string when it is above U+FFFF.
| Input | Result | Comment |
|---|---|---|
"\u{41}" | A | |
"\u{1F600}" | One emoji code point, two UTF-16 units | Same as \uD83D\uDE00. |
"\u{10FFFF}" | The last code point | Highest legal value. |
"\u{110000}" | throws "Invalid code point 1114112" | Above U+10FFFF. |
"\u{4}" | U+0004 | One digit is fine. The README says it "may accept a single hex digit". |
"\u{}" | U+0000 | Zero digits are accepted and give NUL. The README is silent; the code is more permissive than the documentation. |
"\u{D83D}" | A lone surrogate | Accepted, as with the four-digit form. |
"\u{41" | throws "Incomplete long unicode sequence" | Missing closing brace. |
No leading-zero padding limit is applied, so "\u{0000041}" is valid. If you generate JSON6 programmatically and need maximum compatibility with other tools, prefer the four-digit form for characters in the Basic Multilingual Plane and a surrogate pair of four-digit escapes above it, because JSON5 and JSON parsers do not understand the braces form.
Control characters, escape(), and stringify
JSON forbids raw control characters inside strings. JSON6 does not: a literal tab, NUL or other control character is accepted exactly as it appears. This is lenient and convenient for pasted text, but it means a file with an invisible stray control character parses without any warning.
JSON6.escape(string) is a small helper, not a serializer. It prefixes the double quote, single quote, back-tick and backslash characters with a backslash, and nothing else. Its stated purpose is to prevent "JSON injection" when you splice user text into a JSON6 string you are building by hand. It leaves newlines and other control characters as they are.
JSON6.escape('a"b\'c`d\\e') // a\"b\'c\`d\\eJSON6.stringify always writes string values in double quotes. Inside them it backslash-escapes the double quote, the single quote, the back-tick and the backslash, so it's is written as "it\'s". It does not escape newlines or other control characters: a string containing a line feed is written with a literal line break, which is valid JSON6 and invalid JSON. Since 1.1.6, string values are always quoted, even when they look like identifiers. Object keys follow a different rule: a key that is a valid identifier is written bare, and since 1.1.7 a key containing brackets, quotes or whitespace is quoted.
Do not use JSON6.stringify to produce JSON for another system. The escaped single quote \' is illegal in JSON, newlines are not escaped, and keys are unquoted. Use JSON.stringify when a JSON parser will read the result. See the migration tutorial for round-trip advice.
FAQ
Do JSON6 back-tick strings support template literals?
No. A back-tick is only a quote character. The text between two back-ticks is taken literally, including a dollar sign and braces. The forbidTemplateSubstitution option rejects the ${ sequence in back-tick strings if the file might later run as code.
Can I put a real newline inside a quoted string?
Yes, in all three quote styles. The newline is kept in the value. Put a backslash before the newline to remove it. With esStrictCompatible, only back-tick strings may contain a raw newline.
What does an unknown escape like backslash-q do?
It produces the plain character, so "\q" is q. There is no error. That includes backslash followed by a digit, so "\1" is the character 1 and "\012" is NUL followed by 12.
How do I write an emoji in a JSON6 string?
Type it directly in a UTF-8 file, or use the \u{1F600} escape, or the surrogate pair \uD83D\uDE00. The brace form is JSON6-only; the pair works in JSON and JSON5 too.
Is a lone surrogate escape valid?
The parser accepts it, but the resulting string is not well-formed Unicode. Treat it as a bug in your data.
Try the string rules live
Paste a multi-line or back-tick string into the validator to see the parsed value.