Parse and stringify JSON with comments. It will retain comments even when after saved!
Installations
npm install comment-json
Developer
kaelzhang
Developer Guide
Module System
CommonJS
Min. Node Version
>= 6
Typescript Support
Yes
Node Version
21.6.2
NPM Version
10.2.4
Statistics
159 Stars
150 Commits
13 Forks
5 Watching
2 Branches
7 Contributors
Updated on 27 Nov 2024
Bundle Size
146.13 kB
Minified
34.66 kB
Minified + Gzipped
Languages
JavaScript (97.98%)
TypeScript (2.02%)
Total Downloads
Cumulative downloads
Total Downloads
213,146,607
Last day
-3.6%
638,405
Compared to previous day
Last week
1.5%
3,550,219
Compared to previous week
Last month
11.3%
15,076,669
Compared to previous month
Last year
167.5%
126,634,723
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
comment-json
Parse and stringify JSON with comments. It will retain comments even after saved!
- Parse JSON strings with comments into JavaScript objects and MAINTAIN comments
- supports comments everywhere, yes, EVERYWHERE in a JSON file, eventually 😆
- fixes the known issue about comments inside arrays.
- Stringify the objects into JSON strings with comments if there are
The usage of comment-json
is exactly the same as the vanilla JSON
object.
Table of Contents
- Why and How
- Usage and examples
- API reference
- Change Logs
Why?
There are many other libraries that can deal with JSON with comments, such as json5, or strip-json-comments, but none of them can stringify the parsed object and return back a JSON string the same as the original content.
Imagine 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.
So, if you want to parse a JSON string with comments, modify it, then save it back, comment-json
is your must choice!
How?
comment-json
parse JSON strings with comments and save comment tokens into symbol properties.
For JSON array with comments, comment-json
extends the vanilla Array
object into CommentArray
whose instances could handle comments changes even after a comment array is modified.
Install
1$ npm i comment-json
For TypeScript developers, @types/comment-json
could be used
Since 2.4.1
, comment-json
contains typescript declarations, so you might as well remove @types/comment-json
.
Usage
package.json:
1{ 2 // package name 3 "name": "comment-json" 4}
1const { 2 parse, 3 stringify, 4 assign 5} = require('comment-json') 6const fs = require('fs') 7 8const obj = parse(fs.readFileSync('package.json').toString()) 9 10console.log(obj.name) // comment-json 11 12stringify(obj, null, 2) 13// Will be the same as package.json, Oh yeah! 😆 14// which will be very useful if we use a json file to store configurations.
Sort keys
It is a common use case to sort the keys of a JSON file
1const parsed = parse(`{ 2 // b 3 "b": 2, 4 // a 5 "a": 1 6}`) 7 8// Copy the properties including comments from `parsed` to the new object `{}` 9// according to the sequence of the given keys 10const sorted = assign( 11 {}, 12 parsed, 13 // You could also use your custom sorting function 14 Object.keys(parsed).sort() 15) 16 17console.log(stringify(sorted, null, 2)) 18// { 19// // a 20// "a": 1, 21// // b 22// "b": 2 23// }
For details about assign
, see here.
parse()
1parse(text, reviver? = null, remove_comments? = false) 2 : object | string | number | boolean | null
- text
string
The string to parse as JSON. See the JSON object for a description of JSON syntax. - reviver?
Function() | null
Default tonull
. It acts the same as the second parameter ofJSON.parse
. If a function, prescribes how the value originally produced by parsing is transformed, before being returned. - remove_comments?
boolean = false
If true, the comments won't be maintained, which is often used when we want to get a clean object.
Returns CommentJSONValue
(object | string | number | boolean | null
) corresponding to the given JSON text.
If the content
is:
1/** 2 before-all 3 */ 4// before-all 5{ // before:foo 6 // before:foo 7 /* before:foo */ 8 "foo" /* after-prop:foo */: // after-colon:foo 9 1 // after-value:foo 10 // after-value:foo 11 , // after:foo 12 // before:bar 13 "bar": [ // before:0 14 // before:0 15 "baz" // after-value:0 16 // after-value:0 17 , // after:0 18 "quux" 19 // after:1 20 ] // after:bar 21 // after:bar 22} 23// after-all
1const {inspect} = require('util') 2 3const parsed = parse(content) 4 5console.log( 6 inspect(parsed, { 7 // Since 4.0.0, symbol properties of comments are not enumerable, 8 // use `showHidden: true` to print them 9 showHidden: true 10 }) 11) 12 13console.log(Object.keys(parsed)) 14// > ['foo', 'bar'] 15 16console.log(stringify(parsed, null, 2)) 17// 🚀 Exact as the content above! 🚀
And the value of parsed
will be:
1{ 2 // Comments before the JSON object 3 [Symbol.for('before-all')]: [{ 4 type: 'BlockComment', 5 value: '\n before-all\n ', 6 inline: false, 7 loc: { 8 // The start location of `/**` 9 start: { 10 line: 1, 11 column: 0 12 }, 13 // The end location of `*/` 14 end: { 15 line: 3, 16 column: 3 17 } 18 } 19 }, { 20 type: 'LineComment', 21 value: ' before-all', 22 inline: false, 23 loc: ... 24 }], 25 ... 26 27 [Symbol.for('after-prop:foo')]: [{ 28 type: 'BlockComment', 29 value: ' after-prop:foo ', 30 inline: true, 31 loc: ... 32 }], 33 34 // The real value 35 foo: 1, 36 bar: [ 37 "baz", 38 "quux", 39 40 // The property of the array 41 [Symbol.for('after-value:0')]: [{ 42 type: 'LineComment', 43 value: ' after-value:0', 44 inline: true, 45 loc: ... 46 }, ...], 47 ... 48 ] 49}
There are EIGHT kinds of symbol properties:
1// Comments before everything 2Symbol.for('before-all') 3 4// If all things inside an object or an array are comments 5Symbol.for('before') 6 7// comment tokens before 8// - a property of an object 9// - an item of an array 10// and after the previous comma(`,`) or the opening bracket(`{` or `[`) 11Symbol.for(`before:${prop}`) 12 13// comment tokens after property key `prop` and before colon(`:`) 14Symbol.for(`after-prop:${prop}`) 15 16// comment tokens after the colon(`:`) of property `prop` and before property value 17Symbol.for(`after-colon:${prop}`) 18 19// comment tokens after 20// - the value of property `prop` inside an object 21// - the item of index `prop` inside an array 22// and before the next key-value/item delimiter(`,`) 23// or the closing bracket(`}` or `]`) 24Symbol.for(`after-value:${prop}`) 25 26// comment tokens after 27// - comma(`,`) 28// - the value of property `prop` if it is the last property 29Symbol.for(`after:${prop}`) 30 31// Comments after everything 32Symbol.for('after-all')
And the value of each symbol property is an array of CommentToken
1interface CommentToken { 2 type: 'BlockComment' | 'LineComment' 3 // The content of the comment, including whitespaces and line breaks 4 value: string 5 // If the start location is the same line as the previous token, 6 // then `inline` is `true` 7 inline: boolean 8 9 // But pay attention that, 10 // locations will NOT be maintained when stringified 11 loc: CommentLocation 12} 13 14interface CommentLocation { 15 // The start location begins at the `//` or `/*` symbol 16 start: Location 17 // The end location of multi-line comment ends at the `*/` symbol 18 end: Location 19} 20 21interface Location { 22 line: number 23 column: number 24}
Query comments in TypeScript
comment-json
provides a symbol
-type called CommentSymbol
which can be used for querying comments.
Furthermore, a type CommentDescriptor
is provided for enforcing properly formatted symbol names:
1import { 2 CommentDescriptor, CommentSymbol, parse, CommentArray 3} from 'comment-json' 4 5const parsed = parse(`{ /* test */ "foo": "bar" }`) 6 // typescript only allows properly formatted symbol names here 7const symbolName: CommentDescriptor = 'before:foo' 8 9console.log((parsed as CommentArray<string>)[Symbol.for(symbolName) as CommentSymbol][0].value)
In this example, casting to Symbol.for(symbolName)
to CommentSymbol
is mandatory.
Otherwise, TypeScript won't detect that you're trying to query comments.
Parse into an object without comments
1console.log(parse(content, null, true))
And the result will be:
1{ 2 foo: 1, 3 bar: [ 4 "baz", 5 "quux" 6 ] 7}
Special cases
1const parsed = parse(` 2// comment 31 4`) 5 6console.log(parsed === 1) 7// false
If we parse a JSON of primative type with remove_comments:false
, then the return value of parse()
will be of object type.
The value of parsed
is equivalent to:
1const parsed = new Number(1) 2 3parsed[Symbol.for('before-all')] = [{ 4 type: 'LineComment', 5 value: ' comment', 6 inline: false, 7 loc: ... 8}]
Which is similar for:
Boolean
typeString
type
For example
1const parsed = parse(` 2"foo" /* comment */ 3`)
Which is equivalent to
1const parsed = new String('foo') 2 3parsed[Symbol.for('after-all')] = [{ 4 type: 'BlockComment', 5 value: ' comment ', 6 inline: true, 7 loc: ... 8}]
But there is one exception:
1const parsed = parse(` 2// comment 3null 4`) 5 6console.log(parsed === null) // true
stringify()
1stringify(object: any, replacer?, space?): string
The arguments are the same as the vanilla JSON.stringify
.
And it does the similar thing as the vanilla one, but also deal with extra properties and convert them into comments.
1console.log(stringify(parsed, null, 2)) 2// Exactly the same as `content`
space
If space is not specified, or the space is an empty string, the result of stringify()
will have no comments.
For the case above:
1console.log(stringify(result)) // {"a":1} 2console.log(stringify(result, null, 2)) // is the same as `code`
assign(target: object, source?: object, keys?: Array)
- target
object
the target object - source?
object
the source object. This parameter is optional but it is silly to not pass this argument. - keys?
Array<string>
If not specified, all enumerable own properties ofsource
will be used.
This method is used to copy the enumerable own properties and their corresponding comment symbol properties to the target object.
1const parsed = parse(`// before all 2{ 3 // This is a comment 4 "foo": "bar" 5}`) 6 7const obj = assign({ 8 bar: 'baz' 9}, parsed) 10 11stringify(obj, null, 2) 12// // before all 13// { 14// "bar": "baz", 15// // This is a comment 16// "foo": "bar" 17// }
Special cases about keys
But if argument keys
is specified and is not empty, then comment before all
, which belongs to no properties, will NOT be copied.
1const obj = assign({ 2 bar: 'baz' 3}, parsed, ['foo']) 4 5stringify(obj, null, 2) 6// { 7// "bar": "baz", 8// // This is a comment 9// "foo": "bar" 10// }
Specifying the argument keys
as an empty array indicates that it will only copy non-property symbols of comments
1const obj = assign({ 2 bar: 'baz' 3}, parsed, []) 4 5stringify(obj, null, 2) 6// // before all 7// { 8// "bar": "baz", 9// }
Non-property symbols include:
1Symbol.for('before-all') 2Symbol.for('before') 3Symbol.for('after-all')
CommentArray
Advanced Section
All arrays of the parsed object are CommentArray
s.
The constructor of CommentArray
could be accessed by:
1const {CommentArray} = require('comment-json')
If we modify a comment array, its comment symbol properties could be handled automatically.
1const parsed = parse(`{ 2 "foo": [ 3 // bar 4 "bar", 5 // baz, 6 "baz" 7 ] 8}`) 9 10parsed.foo.unshift('qux') 11 12stringify(parsed, null, 2) 13// { 14// "foo": [ 15// "qux", 16// // bar 17// "bar", 18// // baz 19// "baz" 20// ] 21// }
Oh yeah! 😆
But pay attention, if you reassign the property of a comment array with a normal array, all comments will be gone:
1parsed.foo = ['quux'].concat(parsed.foo) 2stringify(parsed, null, 2) 3// { 4// "foo": [ 5// "quux", 6// "qux", 7// "bar", 8// "baz" 9// ] 10// } 11 12// Whoooops!! 😩 Comments are gone
Instead, we should:
1parsed.foo = new CommentArray('quux').concat(parsed.foo) 2stringify(parsed, null, 2) 3// { 4// "foo": [ 5// "quux", 6// "qux", 7// // bar 8// "bar", 9// // baz 10// "baz" 11// ] 12// }
Special Cases about Trailing Comma
If we have a JSON string str
1{ 2 "foo": "bar", // comment 3}
1// When stringify, trailing commas will be eliminated 2const stringified = stringify(parse(str), null, 2) 3console.log(stringified)
And it will print:
1{ 2 "foo": "bar" // comment 3}
License
Change Logs
See releases
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
0 existing vulnerabilities detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Warn: project license file does not contain an FSF or OSI license.
Reason
Found 6/20 approved changesets -- score normalized to 3
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/nodejs.yml:1
- Info: no jobLevel write permissions found
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/kaelzhang/node-comment-json/nodejs.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/kaelzhang/node-comment-json/nodejs.yml/master?enable=pin
- Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:22
- Info: 0 out of 2 GitHub-owned GitHubAction dependencies pinned
- Info: 0 out of 1 npmCommand dependencies pinned
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
branch protection not enabled on development/release branches
Details
- Warn: branch protection not enabled for branch 'master'
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 16 are checked with a SAST tool
Score
3.7
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More