comment-json

Parse and stringify JSON with comments, preserving them exactly through modification and re-save.

Library
npm
v5.0.0
178stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
39/100Needs Attention
Development Activity8
Maintenance20
Community48
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
66/100Good
Architecture78
Code Quality78
Innovation62
Learning Curve45

comment-json is a drop-in replacement for the native JSON.parse/JSON.stringify pair that adds first-class support for comments and blank lines inside JSON documents. Where most JSON-with-comments libraries strip everything down to a comment-free object, comment-json parses the source into a normal JavaScript value while tucking every comment and blank line away as symbol properties, so stringify() can reproduce the original formatting after a program edits the parsed data.

This solves a specific pain point for any tool that reads a JSON config file, changes a couple of values programmatically, and writes it back out — VS Code-style settings, tsconfig-style files, or any hand-annotated .jsonc config. The library also ships a CommentArray subclass that keeps comment positions correct through splice, slice, sort, reverse, and other array mutations, plus helper functions (assign, moveComments, removeComments, removeBlankLines) for common comment-manipulation tasks.

What You Get

  • Comment-preserving parse/stringify - drop-in replacements for JSON.parse/JSON.stringify that retain every line and block comment through a full parse-modify-stringify round trip.
  • CommentArray subclass - an Array extension that keeps comments attached to the right index across splice, slice, push, pop, sort, reverse, and concat.
  • Symbol-keyed comment storage - comments live on nine well-defined Symbol.for(...) properties (before, after-prop, after-colon, etc.) so they never collide with real JSON keys or show up in Object.keys().
  • Comment utility functions - assign, moveComments, removeComments, and removeBlankLines for reordering keys, migrating comments between objects, or stripping comments/blank lines on demand.
  • TypeScript declarations bundled - typed CommentSymbol/CommentDescriptor helpers ship in the package itself, no separate @types package needed since 2.4.1.

Common Use Cases

  • Editing user config files programmatically - a CLI or editor extension needs to bump a version field or add a key to a hand-annotated .json/.jsonc config without wiping out the user’s comments.
  • Building settings-migration tools - a tool that upgrades an app’s saved settings file across versions while leaving explanatory comments intact.
  • Sorting or reorganizing JSON keys - using assign() to reorder an object’s properties (e.g., alphabetizing package.json fields) while comments travel with their original key.
  • Parsing JSONC-style config formats - reading configuration languages that allow comments (similar to tsconfig.json or editor settings.json) where comments carry real documentation value.

Under The Hood

Architecture The library follows a small, monolithic module structure organized around three files under src/: parse.js (a recursive-descent parser built on esprima’s tokenizer), stringify.js (a mirror-image serializer), and array.js (the CommentArray class extending native Array), with common.js holding shared symbol constants and predicates. Parsing uses module-level mutable state (current, last, index, tokens, a comments_host stack) rather than a class instance, functioning as a synchronous state machine that walks tokens from esprima.tokenize and threads a comments-host stack through nested object/array parsing so each nested value’s Symbol-keyed comments attach to the correct owning object; stringify.js mirrors this by walking the JS value graph and re-emitting comment text from those same symbols through a process_comments/join/join_content pipeline that manages line-break bookkeeping. There is no plugin layer — the library is a pure function pair plus one Array subclass — so what breaks if the core Symbol-keyed comment-attachment scheme changes is essentially everything: both parse and stringify, and any consumer reading Symbol.for('before:prop')-style properties directly, are tightly coupled to it.

Tech Stack Runtime dependencies are minimal: esprima for JS/JSON tokenization and array-timsort for the stable sort used by CommentArray.sort() (needed because comment repositioning depends on a predictable index-mapping between old and new order). It is plain CommonJS, targets Node >= 6, and ships hand-written TypeScript declarations (index.d.ts) rather than being authored in TypeScript itself. Dev tooling includes ava as the test runner, nyc for coverage uploaded to Codecov, eslint with a shared config for linting, and typescript used only to typecheck the bundled .d.ts file. CI runs on recent Node versions via GitHub Actions, executing install, lint, test, and a coverage upload; no bundler is needed since the package ships plain JS directly from src/.

Code Quality Tests live under test/*.test.js using ava, covering parse, stringify, array, move-comments, remove-comments, and remove-blank-lines behavior through large tables of description/source/expected-output/assertion fixtures, backed by JSON fixture files. A dedicated TypeScript test typechecks the bundled .d.ts file itself against real usage. Error handling in the parser is explicit: malformed input throws real SyntaxErrors with line/column metadata attached rather than silently returning null or partial data, and no swallowed errors were found in the core parse/stringify path. Naming is internally consistent (snake_case helpers, camelCase public API) and lint is enforced in CI.

API Design comment-json’s public surface deliberately mirrors the native JSON object (parse, stringify with the same signatures), which minimizes the learning curve for anyone already using JSON.parse/JSON.stringify — adoption is close to a drop-in swap. Where it differs from a plain JSON library, it does so narrowly and documents each addition (the nine comment-symbol positions, the CommentArray mutation methods, and the small set of helper functions) rather than growing a large surface area, keeping the boilerplate required to start using it close to zero beyond an npm install.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search