{"id":15633748,"url":"https://github.com/kaelzhang/node-comment-json","last_synced_at":"2025-05-16T02:09:23.847Z","repository":{"id":17938730,"uuid":"20915670","full_name":"kaelzhang/node-comment-json","owner":"kaelzhang","description":"Parse and stringify JSON with comments. It will retain comments even when after saved!","archived":false,"fork":false,"pushed_at":"2024-08-11T13:01:51.000Z","size":208,"stargazers_count":164,"open_issues_count":9,"forks_count":13,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-08T12:02:19.504Z","etag":null,"topics":["comment-json","json","json-parse","json-stringify"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kaelzhang.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-06-17T08:22:19.000Z","updated_at":"2025-01-27T03:24:47.000Z","dependencies_parsed_at":"2024-11-28T15:32:47.025Z","dependency_job_id":"8138ebb9-25d7-4df3-847e-9ea125a720b0","html_url":"https://github.com/kaelzhang/node-comment-json","commit_stats":{"total_commits":142,"total_committers":8,"mean_commits":17.75,"dds":0.4154929577464789,"last_synced_commit":"0f4f85a325222fc2ee42a9f35acf174deb27b74f"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaelzhang%2Fnode-comment-json","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaelzhang%2Fnode-comment-json/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaelzhang%2Fnode-comment-json/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaelzhang%2Fnode-comment-json/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kaelzhang","download_url":"https://codeload.github.com/kaelzhang/node-comment-json/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243826794,"owners_count":20354220,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["comment-json","json","json-parse","json-stringify"],"created_at":"2024-10-03T10:50:08.826Z","updated_at":"2025-03-25T08:09:39.653Z","avatar_url":"https://github.com/kaelzhang.png","language":"JavaScript","readme":"[![Build Status](https://github.com/kaelzhang/node-comment-json/actions/workflows/nodejs.yml/badge.svg)](https://github.com/kaelzhang/node-comment-json/actions/workflows/nodejs.yml)\n[![Coverage](https://codecov.io/gh/kaelzhang/node-comment-json/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/node-comment-json)\n[![npm module downloads per month](http://img.shields.io/npm/dm/comment-json.svg)](https://www.npmjs.org/package/comment-json)\n\u003c!-- optional appveyor tst\n[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kaelzhang/node-comment-json?branch=master\u0026svg=true)](https://ci.appveyor.com/project/kaelzhang/node-comment-json)\n--\u003e\n\u003c!-- optional npm version\n[![NPM version](https://badge.fury.io/js/comment-json.svg)](http://badge.fury.io/js/comment-json)\n--\u003e\n\u003c!-- optional dependency status\n[![Dependency Status](https://david-dm.org/kaelzhang/node-comment-json.svg)](https://david-dm.org/kaelzhang/node-comment-json)\n--\u003e\n\n# comment-json\n\nParse and stringify JSON with comments. It will retain comments even after saved!\n\n- [Parse](#parse) JSON strings with comments into JavaScript objects and MAINTAIN comments\n  - supports comments everywhere, yes, **EVERYWHERE** in a JSON file, eventually 😆\n  - fixes the known issue about comments inside arrays.\n- [Stringify](#stringify) the objects into JSON strings with comments if there are\n\nThe usage of `comment-json` is exactly the same as the vanilla [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) object.\n\n## Table of Contents\n\n- [Why](#why) and [How](#how)\n- [Usage and examples](#usage)\n- API reference\n  - [parse](#parse)\n  - [stringify](#stringify)\n  - [assign](#assigntarget-object-source-object-keys-array)\n  - [CommentArray](#commentarray)\n- [Change Logs](https://github.com/kaelzhang/node-comment-json/releases)\n\n## Why?\n\nThere are many other libraries that can deal with JSON with comments, such as [json5](https://npmjs.org/package/json5), or [strip-json-comments](https://npmjs.org/package/strip-json-comments), but none of them can stringify the parsed object and return back a JSON string the same as the original content.\n\nImagine that if the user settings are saved in `${library}.json`， and the user has written a lot of comments to improve readability. If the library `library` need to modify the user setting, such as modifying some property values and adding new fields, and if the library uses `json5` to read the settings, all comments will disappear after modified which will drive people insane.\n\nSo, **if you want to parse a JSON string with comments, modify it, then save it back**, `comment-json` is your must choice!\n\n## How?\n\n`comment-json` parse JSON strings with comments and save comment tokens into [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) properties.\n\nFor JSON array with comments, `comment-json` extends the vanilla `Array` object into [`CommentArray`](#commentarray) whose instances could handle comments changes even after a comment array is modified.\n\n## Install\n\n```sh\n$ npm i comment-json\n```\n\n~~For TypeScript developers, [`@types/comment-json`](https://www.npmjs.com/package/@types/comment-json) could be used~~\n\nSince `2.4.1`, `comment-json` contains typescript declarations, so you might as well remove `@types/comment-json`.\n\n## Usage\n\npackage.json:\n\n```js\n{\n  // package name\n  \"name\": \"comment-json\"\n}\n```\n\n```js\nconst {\n  parse,\n  stringify,\n  assign\n} = require('comment-json')\nconst fs = require('fs')\n\nconst obj = parse(fs.readFileSync('package.json').toString())\n\nconsole.log(obj.name) // comment-json\n\nstringify(obj, null, 2)\n// Will be the same as package.json, Oh yeah! 😆\n// which will be very useful if we use a json file to store configurations.\n```\n\n### Sort keys\n\nIt is a common use case to sort the keys of a JSON file\n\n```js\nconst parsed = parse(`{\n  // b\n  \"b\": 2,\n  // a\n  \"a\": 1\n}`)\n\n// Copy the properties including comments from `parsed` to the new object `{}`\n// according to the sequence of the given keys\nconst sorted = assign(\n  {},\n  parsed,\n  // You could also use your custom sorting function\n  Object.keys(parsed).sort()\n)\n\nconsole.log(stringify(sorted, null, 2))\n// {\n//   // a\n//   \"a\": 1,\n//   // b\n//   \"b\": 2\n// }\n```\n\nFor details about `assign`, see [here](#assigntarget-object-source-object-keys-array).\n\n## parse()\n\n```ts\nparse(text, reviver? = null, remove_comments? = false)\n  : object | string | number | boolean | null\n```\n\n- **text** `string` The string to parse as JSON. See the [JSON](http://json.org/) object for a description of JSON syntax.\n- **reviver?** `Function() | null` Default to `null`. It acts the same as the second parameter of [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse). If a function, prescribes how the value originally produced by parsing is transformed, before being returned.\n- **remove_comments?** `boolean = false` If true, the comments won't be maintained, which is often used when we want to get a clean object.\n\nReturns `CommentJSONValue` (`object | string | number | boolean | null`) corresponding to the given JSON text.\n\nIf the `content` is:\n\n```js\n/**\n before-all\n */\n// before-all\n{ // before:foo\n  // before:foo\n  /* before:foo */\n  \"foo\" /* after-prop:foo */: // after-colon:foo\n  1 // after-value:foo\n  // after-value:foo\n  , // after:foo\n  // before:bar\n  \"bar\": [ // before:0\n    // before:0\n    \"baz\" // after-value:0\n    // after-value:0\n    , // after:0\n    \"quux\"\n    // after:1\n  ] // after:bar\n  // after:bar\n}\n// after-all\n```\n\n```js\nconst {inspect} = require('util')\n\nconst parsed = parse(content)\n\nconsole.log(\n  inspect(parsed, {\n    // Since 4.0.0, symbol properties of comments are not enumerable,\n    // use `showHidden: true` to print them\n    showHidden: true\n  })\n)\n\nconsole.log(Object.keys(parsed))\n// \u003e ['foo', 'bar']\n\nconsole.log(stringify(parsed, null, 2))\n// 🚀 Exact as the content above! 🚀\n```\n\nAnd the value of `parsed` will be:\n\n```js\n{\n  // Comments before the JSON object\n  [Symbol.for('before-all')]: [{\n    type: 'BlockComment',\n    value: '\\n before-all\\n ',\n    inline: false,\n    loc: {\n      // The start location of `/**`\n      start: {\n        line: 1,\n        column: 0\n      },\n      // The end location of `*/`\n      end: {\n        line: 3,\n        column: 3\n      }\n    }\n  }, {\n    type: 'LineComment',\n    value: ' before-all',\n    inline: false,\n    loc: ...\n  }],\n  ...\n\n  [Symbol.for('after-prop:foo')]: [{\n    type: 'BlockComment',\n    value: ' after-prop:foo ',\n    inline: true,\n    loc: ...\n  }],\n\n  // The real value\n  foo: 1,\n  bar: [\n    \"baz\",\n    \"quux\",\n\n    // The property of the array\n    [Symbol.for('after-value:0')]: [{\n      type: 'LineComment',\n      value: ' after-value:0',\n      inline: true,\n    loc: ...\n    }, ...],\n    ...\n  ]\n}\n```\n\nThere are **EIGHT** kinds of symbol properties:\n\n```js\n// Comments before everything\nSymbol.for('before-all')\n\n// If all things inside an object or an array are comments\nSymbol.for('before')\n\n// comment tokens before\n// - a property of an object\n// - an item of an array\n// and after the previous comma(`,`) or the opening bracket(`{` or `[`)\nSymbol.for(`before:${prop}`)\n\n// comment tokens after property key `prop` and before colon(`:`)\nSymbol.for(`after-prop:${prop}`)\n\n// comment tokens after the colon(`:`) of property `prop` and before property value\nSymbol.for(`after-colon:${prop}`)\n\n// comment tokens after\n// - the value of property `prop` inside an object\n// - the item of index `prop` inside an array\n// and before the next key-value/item delimiter(`,`)\n// or the closing bracket(`}` or `]`)\nSymbol.for(`after-value:${prop}`)\n\n// comment tokens after\n// - comma(`,`)\n// - the value of property `prop` if it is the last property\nSymbol.for(`after:${prop}`)\n\n// Comments after everything\nSymbol.for('after-all')\n```\n\nAnd the value of each symbol property is an **array** of `CommentToken`\n\n```ts\ninterface CommentToken {\n  type: 'BlockComment' | 'LineComment'\n  // The content of the comment, including whitespaces and line breaks\n  value: string\n  // If the start location is the same line as the previous token,\n  // then `inline` is `true`\n  inline: boolean\n\n  // But pay attention that,\n  // locations will NOT be maintained when stringified\n  loc: CommentLocation\n}\n\ninterface CommentLocation {\n  // The start location begins at the `//` or `/*` symbol\n  start: Location\n  // The end location of multi-line comment ends at the `*/` symbol\n  end: Location\n}\n\ninterface Location {\n  line: number\n  column: number\n}\n```\n\n### Query comments in TypeScript\n\n`comment-json` provides a `symbol`-type called `CommentSymbol` which can be used for querying comments.\nFurthermore, a type `CommentDescriptor` is provided for enforcing properly formatted symbol names:\n\n```ts\nimport {\n  CommentDescriptor, CommentSymbol, parse, CommentArray\n} from 'comment-json'\n\nconst parsed = parse(`{ /* test */ \"foo\": \"bar\" }`)\n // typescript only allows properly formatted symbol names here\nconst symbolName: CommentDescriptor = 'before:foo'\n\nconsole.log((parsed as CommentArray\u003cstring\u003e)[Symbol.for(symbolName) as CommentSymbol][0].value)\n```\n\nIn this example, casting to `Symbol.for(symbolName)` to `CommentSymbol` is mandatory.\nOtherwise, TypeScript won't detect that you're trying to query comments.\n\n### Parse into an object without comments\n\n```js\nconsole.log(parse(content, null, true))\n```\n\nAnd the result will be:\n\n```js\n{\n  foo: 1,\n  bar: [\n    \"baz\",\n    \"quux\"\n  ]\n}\n```\n\n### Special cases\n\n```js\nconst parsed = parse(`\n// comment\n1\n`)\n\nconsole.log(parsed === 1)\n// false\n```\n\nIf we parse a JSON of primative type with `remove_comments:false`, then the return value of `parse()` will be of object type.\n\nThe value of `parsed` is equivalent to:\n\n```js\nconst parsed = new Number(1)\n\nparsed[Symbol.for('before-all')] = [{\n  type: 'LineComment',\n  value: ' comment',\n  inline: false,\n  loc: ...\n}]\n```\n\nWhich is similar for:\n\n- `Boolean` type\n- `String` type\n\nFor example\n\n```js\nconst parsed = parse(`\n\"foo\" /* comment */\n`)\n```\n\nWhich is equivalent to\n\n```js\nconst parsed = new String('foo')\n\nparsed[Symbol.for('after-all')] = [{\n  type: 'BlockComment',\n  value: ' comment ',\n  inline: true,\n  loc: ...\n}]\n```\n\nBut there is one exception:\n\n```js\nconst parsed = parse(`\n// comment\nnull\n`)\n\nconsole.log(parsed === null) // true\n```\n\n## stringify()\n\n```ts\nstringify(object: any, replacer?, space?): string\n```\n\nThe arguments are the same as the vanilla [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).\n\nAnd it does the similar thing as the vanilla one, but also deal with extra properties and convert them into comments.\n\n```js\nconsole.log(stringify(parsed, null, 2))\n// Exactly the same as `content`\n```\n\n#### space\n\nIf space is not specified, or the space is an empty string, the result of `stringify()` will have no comments.\n\nFor the case above:\n\n```js\nconsole.log(stringify(result)) // {\"a\":1}\nconsole.log(stringify(result, null, 2)) // is the same as `code`\n```\n\n## assign(target: object, source?: object, keys?: Array\u003cstring\u003e)\n\n- **target** `object` the target object\n- **source?** `object` the source object. This parameter is optional but it is silly to not pass this argument.\n- **keys?** `Array\u003cstring\u003e` If not specified, all enumerable own properties of `source` will be used.\n\nThis method is used to copy the enumerable own properties and their corresponding comment symbol properties to the target object.\n\n```js\nconst parsed = parse(`// before all\n{\n  // This is a comment\n  \"foo\": \"bar\"\n}`)\n\nconst obj = assign({\n  bar: 'baz'\n}, parsed)\n\nstringify(obj, null, 2)\n// // before all\n// {\n//   \"bar\": \"baz\",\n//   // This is a comment\n//   \"foo\": \"bar\"\n// }\n```\n\n### Special cases about `keys`\n\nBut if argument `keys` is specified and is not empty, then comment ` before all`, which belongs to no properties, will **NOT** be copied.\n\n```js\nconst obj = assign({\n  bar: 'baz'\n}, parsed, ['foo'])\n\nstringify(obj, null, 2)\n// {\n//   \"bar\": \"baz\",\n//   // This is a comment\n//   \"foo\": \"bar\"\n// }\n```\n\nSpecifying the argument `keys` as an empty array indicates that it will only copy non-property symbols of comments\n\n```js\nconst obj = assign({\n  bar: 'baz'\n}, parsed, [])\n\nstringify(obj, null, 2)\n// // before all\n// {\n//   \"bar\": \"baz\",\n// }\n```\n\nNon-property symbols include:\n\n```js\nSymbol.for('before-all')\nSymbol.for('before')\nSymbol.for('after-all')\n```\n\n## `CommentArray`\n\n\u003e Advanced Section\n\nAll arrays of the parsed object are `CommentArray`s.\n\nThe constructor of `CommentArray` could be accessed by:\n\n```js\nconst {CommentArray} = require('comment-json')\n```\n\nIf we modify a comment array, its comment symbol properties could be handled automatically.\n\n```js\nconst parsed = parse(`{\n  \"foo\": [\n    // bar\n    \"bar\",\n    // baz,\n    \"baz\"\n  ]\n}`)\n\nparsed.foo.unshift('qux')\n\nstringify(parsed, null, 2)\n// {\n//   \"foo\": [\n//     \"qux\",\n//     // bar\n//     \"bar\",\n//     // baz\n//     \"baz\"\n//   ]\n// }\n```\n\nOh yeah! 😆\n\nBut pay attention, if you reassign the property of a comment array with a normal array, all comments will be gone:\n\n```js\nparsed.foo = ['quux'].concat(parsed.foo)\nstringify(parsed, null, 2)\n// {\n//   \"foo\": [\n//     \"quux\",\n//     \"qux\",\n//     \"bar\",\n//     \"baz\"\n//   ]\n// }\n\n// Whoooops!! 😩 Comments are gone\n```\n\nInstead, we should:\n\n```js\nparsed.foo = new CommentArray('quux').concat(parsed.foo)\nstringify(parsed, null, 2)\n// {\n//   \"foo\": [\n//     \"quux\",\n//     \"qux\",\n//     // bar\n//     \"bar\",\n//     // baz\n//     \"baz\"\n//   ]\n// }\n```\n\n## Special Cases about Trailing Comma\n\nIf we have a JSON string `str`\n\n```js\n{\n  \"foo\": \"bar\", // comment\n}\n```\n\n```js\n// When stringify, trailing commas will be eliminated\nconst stringified = stringify(parse(str), null, 2)\nconsole.log(stringified)\n```\n\nAnd it will print:\n\n```js\n{\n  \"foo\": \"bar\" // comment\n}\n```\n\n## License\n\n[MIT](LICENSE)\n\n## Change Logs\n\nSee [releases](https://github.com/kaelzhang/node-comment-json/releases)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaelzhang%2Fnode-comment-json","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkaelzhang%2Fnode-comment-json","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaelzhang%2Fnode-comment-json/lists"}