json5
json5 is the reference parser and serializer for JSON5, a superset of JSON built for files humans edit by hand: comments, trailing commas, single quotes, unquoted keys, hex numbers, and multi-line strings are all legal. The API mirrors the built-in JSON object, so JSON5.parse and JSON5.stringify are drop-in familiar, and every valid JSON file is valid JSON5. It exists for config files, and its own README says it is not intended for machine-to-machine communication. Babel, Chromium, and Next.js use it for exactly that config-file role, which is where the enormous download count comes from.
The definitive implementation of a genuinely useful config format: stable, dependency-free, and API-identical to JSON. Treat the frozen release cadence as what it is, a finished spec implementation, and reach for jsonc-parser or comment-json when your real problem is editor-style JSONC or comment-preserving rewrites.
Use it if
- You want config files users edit by hand, with comments and trailing commas, while keeping JSON's data model and a parse call that works like JSON.parse
- You need to read existing .json5 files from ecosystems that adopted the format (Babel config, Chromium feature files, projects using the .json5 extension)
- You want a CLI sanity layer: the bundled json5 binary converts JSON5 to JSON and validates syntax in CI without any code
- You need the same parser in Node and the browser: it ships CJS, ESM, and UMD builds with zero dependencies
- The data is machine-to-machine or hot-path: the hand-written parser is far slower than native JSON.parse, and the README itself tells you to keep using JSON for that
- Your 'JSON with comments' files are actually JSONC (tsconfig.json, VS Code settings): that is a different, looser grammar, and jsonc-parser is the tool that matches editor behavior
- You need comments to survive a read-modify-write cycle: JSON5.parse throws comments away and stringify cannot put them back; comment-json exists precisely for that round trip
- You want an actively evolving project: the last npm release (2.2.3) shipped in December 2022 and the repo's last push was October 2024; it is finished and stable, but issues and PRs mostly sit
Setup reality
npm install json5 and either require or import it; zero dependencies, TypeScript types bundled, Node 6+ per package engines, plus a UMD build for script tags. Nothing to configure. The sharp edges are behavioral: parse discards comments and stringify emits JSON5 (unquoted keys, single quotes) rather than JSON, which surprises people expecting a prettier JSON.stringify; pass the quote option or use the CLI if downstream tools need strict JSON. Versions before 2.2.2 had a prototype pollution bug in parse (CVE-2022-46175), so make sure resolutions do not pin something ancient. The require('json5/lib/register') hook mutates Node's global module loading, so keep it out of libraries.
Patterns
Parse a JSON5 config fileparse-config
const JSON5 = require('json5')
const fs = require('fs')
const config = JSON5.parse(fs.readFileSync('app.json5', 'utf8'))
// file may contain comments, trailing commas,
// single quotes, unquoted keys, hex numbersparse returns plain objects and throws SyntaxError on bad input, like JSON.parse. Comments are discarded, not represented in the result.
Use json5 from ESM or the browseresm-import
import JSON5 from 'json5'
const data = JSON5.parse("{hello: 'world', trailing: 'comma',}")
// browser without a bundler:
// <script src="https://unpkg.com/json5@2/dist/index.min.js"></script>
// then use the global JSON5The package ships CJS, ESM (.mjs), and UMD builds, so both module systems work without flags; there are no named exports, only the default JSON5 object.
Transform values while parsingparse-with-reviver
const JSON5 = require('json5')
const doc = JSON5.parse('{start: "2026-08-05", retries: 3}', (key, value) => {
if (key === 'start') return new Date(value)
return value
})Same reviver contract as JSON.parse: called bottom-up per key, returning undefined deletes the property. Returning undefined for the root gives you undefined back.
Serialize with indentationstringify-pretty
const JSON5 = require('json5')
const out = JSON5.stringify({ name: 'app', tags: ['a', 'b'] }, null, 2)
// {
// name: 'app',
// tags: [
// 'a',
// 'b',
// ],
// }Output is JSON5, not JSON: keys are unquoted when legal, strings use single quotes, and indented output gets trailing commas. Do not feed this to a strict JSON consumer.
Serialize with double quotesstringify-double-quotes
const JSON5 = require('json5')
const out = JSON5.stringify(
{ message: "it's fine" },
{ space: 2, quote: '"' }
)
// keys still unquoted where legal, strings use "The options-object form accepts replacer, space, and quote. There is no option to force-quote keys; if you need strict JSON output, use JSON.stringify on the parsed value instead.
Filter or transform during stringifystringify-replacer
const JSON5 = require('json5')
// whitelist properties
JSON5.stringify(user, ['name', 'email'], 2)
// or transform values
JSON5.stringify(user, (key, value) =>
key === 'password' ? undefined : value, 2)Same semantics as JSON.stringify: undefined, functions, and symbols are dropped (or nulled inside arrays). Unlike JSON, Infinity and NaN serialize as-is instead of becoming null.
Report syntax errors with positionhandle-parse-errors
const JSON5 = require('json5')
try {
JSON5.parse("{ bad json5 ")
} catch (err) {
console.error(err.message) // includes position info
console.error(err.lineNumber, err.columnNumber)
}Errors are SyntaxError instances with lineNumber and columnNumber properties, which makes user-facing config error messages much better than native JSON.parse offers.
require() .json5 files directly in Noderequire-json5-files
require('json5/lib/register')
const config = require('./config.json5')The register hook patches Node's CJS loader globally for the process, so use it in apps and scripts, never inside published libraries. There is no equivalent for ESM import.
Convert and validate from the command linecli-convert
npm install --global json5
# convert JSON5 to JSON
json5 config.json5 --out-file config.json --space 2
# validate only, exit code signals success
json5 --validate config.json5
# stdin works too
cat config.json5 | json5The CLI is the clean bridge to tools that demand strict JSON: keep the JSON5 source in git, generate JSON at build time.
Know what the format allowsjson5-feature-showcase
const JSON5 = require('json5')
const obj = JSON5.parse(`{
// comments work
unquoted: 'and single quotes',
hex: 0xDECAF,
leading: .5,
positive: +1,
notANumber: NaN,
infinity: Infinity,
trailing: 'comma',
}`)NaN and Infinity parse to real JavaScript values, and hex parses to plain numbers. None of this survives a round trip through strict JSON.stringify (NaN/Infinity become null).
Parse untrusted input safelysafe-parse-untrusted
const JSON5 = require('json5')
// ensure version >= 2.2.2 (CVE-2022-46175 fixed __proto__ pollution)
const data = JSON5.parse(untrusted)
const clean = Object.assign(Object.create(null), data)Versions before 2.2.2 let a __proto__ key in input pollute the result's prototype. Current versions parse it as a plain own property, matching JSON.parse; still avoid JSON5 for hostile machine input since that is not its job.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| jsonc-parser | npm | You are reading tsconfig/VS Code style JSONC and want the same error-tolerant parser and edit operations the editor uses. |
| comment-json | npm | You must parse, modify, and re-serialize a config while preserving its comments and formatting intent. |
| yaml | npm | Your hand-edited config wants comments plus anchors, multi-line strings, and richer structure, and your team accepts YAML's own footguns. |