# How to accept comments and unquoted keys in a JSON config file

> Parse a hand-edited config with a lenient JSON dialect, so comments, trailing commas and unquoted keys load instead of failing on one character.

Source: https://voxgig.com/howto/accept-comments-and-unquoted-keys-in-json

- Audience: platform-team
- Level: beginner
- Languages: typescript, javascript
- Verified: 2026-09-06
- Published: 2026-09-06

## Short answer

Parse the file with a JSON superset rather than JSON.parse. Comments, trailing commas, unquoted keys and single-quoted strings then load, and any document that is valid JSON still reads to the same value. Keep the leniency in the parser and the strictness in a validator that runs on the parsed value, so a typo is still an error.

---
## You will need

Node 22 or later, and a config file that people edit. The example uses
[jsonic](https://jsonic.senecajs.org), which is a JSON superset rather than a separate format: every
JSON document is already a jsonic document.

## Approaches compared

| Approach | When it fits | What it costs you | When to pick something else |
| --- | --- | --- | --- |
| [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) | Machine-written files, and wire formats nobody edits | A comment or a trailing comma is a startup failure with a character offset for a message | A person edits the file |
| [JSON5](https://json5.org/) | You want comments and trailing commas with a written specification behind them | Members still need commas between them, so a list of settings on their own lines fails | The file style omits commas between entries |
| [jsonc-parser](https://github.com/microsoft/node-jsonc-parser) | Editor tooling, where you need the parse tree and error recovery | Comments and trailing commas only, and an API built for tooling rather than for loading | You want the value and nothing else |
| [jsonic](https://jsonic.senecajs.org) | Files people write by hand, where commas and quotes are noise | A dialect with no external specification, so another language may not have a reader for it | The same file must be parsed by several languages |

Voxgig maintains jsonic. It is one of four options here, not the recommendation.

The choice is how far from JSON you want the file to sit. JSON5 is a published specification with
readers in several languages, and it keeps JSON's punctuation. jsonic drops more of the punctuation,
which suits a file a person writes and makes the file harder to consume outside its own ecosystem.

## Parse with the superset

Loading is one call, and the important property is what it does to files that are already JSON.

```ts title="load.mjs"
export function loadConfig(path) {
  return Jsonic(readFileSync(path, 'utf8'))
}
```

Everything valid in JSON parses to the same value, so adopting the superset is not a migration.
Existing files keep working, and the leniency only matters for files somebody chooses to write in
the looser style. Nothing has to be converted, and nothing breaks on the day the dependency lands in the lockfile.

That property is what makes the change reversible. A file written in the loose style can be
tightened back into JSON by adding the quotes and commas, and the parser reads it either way. Teams
that adopt a dialect and then want out usually find the reverse is not true, so it is worth checking
before the first file is written rather than after two hundred.

## See what each parser accepts

The file below has comments, single quotes, a trailing comma, and members separated by newlines
rather than commas.

```bash
node compare.mjs
```

```text output
JSON.parse   failed: Unexpected token '/', "// The por"... is not valid JSON
JSON5.parse  failed: JSON5: invalid character 'p' at 2:1
Jsonic       parsed, port 8080, pool max 20
```

JSON5 handles the comments and the trailing comma, and stops at the missing comma after the first
setting. That is the boundary between the two. JSON5 relaxes quoting and comments, and jsonic also relaxes
the separators. Put commas back in the file and JSON5 reads it.

## Keep the strictness somewhere

A lenient parser makes a class of typo silent, so the strictness has to move rather than disappear.

```bash
node --test load.test.mjs
```

```text output
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 155.35414
```

Two of those tests are about the limits rather than the leniency. A bracket that never closes is still an error. A key with no value is not, and it parses to null,
which is a value your validator has to reject rather than your parser.

## When it goes wrong

A missing value becomes null and nothing complains. Writing `max:` with nothing after it produces
`{ max: null }`, and the service starts with a null where a number belongs. Validate the parsed
object against a schema before using it, and that class of typo fails at startup with the property
name instead of surfacing later as a strange error.

The second surprise is the prototype. Objects come back with a null prototype, so `hasOwnProperty`
called on one throws and a deep equality check against a plain object fails. Code that spreads,
serializes or reads properties never notices, and a test comparing parsed output to a literal does.

The third is a file that means something different from what it looks like. Dropping commas puts
weight on line breaks, so reformatting a long value onto two lines can change the parse. Run the loader over every config file in the repository once, and compare each result against what
the previous parser produced. That is a short script, and it is the only way to know the migration
changed no values. Keep it as a test if config files are added often.

## When not to do this

Do not use jsonic for data on the wire. Request and response bodies are written by programs, and a
parser that accepts what JSON refuses widens what a caller can send you without widening what you
meant to accept.

Do not adopt a superset in a file another language has to read. A Go service and a Python job both
reading the same config file need a format with a reader in both, and JSON or
[TOML](https://toml.io/en/) is the answer there.

Do not let leniency substitute for validation. Parsing decides whether the text is well formed, and
validation decides whether the values make sense, and a lenient parser makes the second job larger
rather than smaller. Run a schema over the parsed object and report the failures together, so one
edit fixes every complaint rather than one per restart.

Do not switch formats to gain comments alone. If the only thing missing is explanation, adding a
`_comment` key or moving the explanation to a README costs less than a new dependency and a dialect
your team has to learn.

## Related how-tos

- [Write a validator whose schema looks like the data](/howto/write-a-schema-that-looks-like-the-data)

- [Merge nested config objects with predictable precedence](/howto/merge-nested-config-objects-with-precedence)

## Last verified

Verified 2026-09-06 against Node 22.22.2, @tabnas/jsonic 0.6.6 and json5 2.2.3. Both output blocks
are what the preceding command printed, against the config file checked in beside them.