1 | [![Build Status](https://travis-ci.org/kaelzhang/node-comment-json.svg?branch=master)](https://travis-ci.org/kaelzhang/node-comment-json)
|
2 | [![Coverage](https://codecov.io/gh/kaelzhang/node-comment-json/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/node-comment-json)
|
3 |
|
4 | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kaelzhang/node-comment-json?branch=master&svg=true)](https://ci.appveyor.com/project/kaelzhang/node-comment-json)
|
5 | -->
|
6 |
|
7 | [![NPM version](https://badge.fury.io/js/comment-json.svg)](http://badge.fury.io/js/comment-json)
|
8 | -->
|
9 |
|
10 | [![npm module downloads per month](http://img.shields.io/npm/dm/comment-json.svg)](https://www.npmjs.org/package/comment-json)
|
11 | -->
|
12 |
|
13 | [![Dependency Status](https://david-dm.org/kaelzhang/node-comment-json.svg)](https://david-dm.org/kaelzhang/node-comment-json)
|
14 | -->
|
15 |
|
16 | # comment-json
|
17 |
|
18 | Parse and stringify JSON with comments. It will retain comments even after saved!
|
19 |
|
20 | - [Parse](#parse) JSON strings with comments into JavaScript objects and MAINTAIN comments
|
21 | - supports comments everywhere, yes, **EVERYWHERE** in a JSON file, eventually 😆
|
22 | - fixes the known issue about comments inside arrays.
|
23 | - [Stringify](#stringify) the objects into JSON strings with comments if there are
|
24 |
|
25 | The 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.
|
26 |
|
27 | ## Why?
|
28 |
|
29 | There 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.
|
30 |
|
31 | 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.
|
32 |
|
33 | So, **if you want to parse a JSON string with comments, modify it, then save it back**, `comment-json` is your must choice!
|
34 |
|
35 | ## How?
|
36 |
|
37 | `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.
|
38 |
|
39 | For 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.
|
40 |
|
41 | ## Install
|
42 |
|
43 | ```sh
|
44 | $ npm i comment-json
|
45 | ```
|
46 |
|
47 | For TypeScript developers, [`@types/comment-json`](https://www.npmjs.com/package/@types/comment-json) could be used.
|
48 |
|
49 | ## Usage
|
50 |
|
51 | package.json:
|
52 |
|
53 | ```js
|
54 | {
|
55 | // package name
|
56 | "name": "comment-json"
|
57 | }
|
58 | ```
|
59 |
|
60 | ```js
|
61 | const {
|
62 | parse,
|
63 | stringify,
|
64 | assign
|
65 | } = require('comment-json')
|
66 | const fs = require('fs')
|
67 |
|
68 | const obj = parse(fs.readFileSync('package.json').toString())
|
69 |
|
70 | console.log(obj.name) // comment-json
|
71 |
|
72 | stringify(obj, null, 2)
|
73 | // Will be the same as package.json, Oh yeah! 😆
|
74 | // which will be very useful if we use a json file to store configurations.
|
75 | ```
|
76 |
|
77 | ## parse()
|
78 |
|
79 | ```ts
|
80 | parse(text, reviver? = null, remove_comments? = false)
|
81 | : object | string | number | boolean | null
|
82 | ```
|
83 |
|
84 | - **text** `string` The string to parse as JSON. See the [JSON](http://json.org/) object for a description of JSON syntax.
|
85 | - **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.
|
86 | - **remove_comments?** `boolean = false` If true, the comments won't be maintained, which is often used when we want to get a clean object.
|
87 |
|
88 | Returns `object | string | number | boolean | null` corresponding to the given JSON text.
|
89 |
|
90 | If the `content` is:
|
91 |
|
92 | ```js
|
93 | /**
|
94 | before-all
|
95 | */
|
96 | // before-all
|
97 | { // before:foo
|
98 | // before:foo
|
99 | /* before:foo */
|
100 | "foo" /* after-prop:foo */: // after-colon:foo
|
101 | 1 // after-value:foo
|
102 | // after-value:foo
|
103 | , // after:foo
|
104 | // before:bar
|
105 | "bar": [ // before:0
|
106 | // before:0
|
107 | "baz" // after-value:0
|
108 | // after-value:0
|
109 | , // after:0
|
110 | "quux"
|
111 | // after:1
|
112 | ] // after:bar
|
113 | // after:bar
|
114 | }
|
115 | // after-all
|
116 | ```
|
117 |
|
118 | ```js
|
119 | const {inspect} = require('util')
|
120 |
|
121 | const parsed = parse(content)
|
122 |
|
123 | console.log(
|
124 | inspect(parsed, {
|
125 | // Since 4.0.0, symbol properties of comments are not enumerable,
|
126 | // use `showHidden: true` to print them
|
127 | showHidden: true
|
128 | })
|
129 | )
|
130 |
|
131 | console.log(Object.keys(parsed))
|
132 | // > ['foo', 'bar']
|
133 |
|
134 | console.log(stringify(parsed, null, 2))
|
135 | // 🚀 Exact as the content above! 🚀
|
136 | ```
|
137 |
|
138 | And the value of `parsed` will be:
|
139 |
|
140 | ```js
|
141 | {
|
142 | // Comments before the JSON object
|
143 | [Symbol.for('before-all')]: [{
|
144 | type: 'BlockComment',
|
145 | value: '\n before-all\n ',
|
146 | inline: false,
|
147 | loc: {
|
148 | // The start location of `/**`
|
149 | start: {
|
150 | line: 1,
|
151 | column: 0
|
152 | },
|
153 | // The end location of `*/`
|
154 | end: {
|
155 | line: 3,
|
156 | column: 3
|
157 | }
|
158 | }
|
159 | }, {
|
160 | type: 'LineComment',
|
161 | value: ' before-all',
|
162 | inline: false,
|
163 | loc: ...
|
164 | }],
|
165 | ...
|
166 |
|
167 | [Symbol.for('after-prop:foo')]: [{
|
168 | type: 'BlockComment',
|
169 | value: ' after-prop:foo ',
|
170 | inline: true,
|
171 | loc: ...
|
172 | }],
|
173 |
|
174 | // The real value
|
175 | foo: 1,
|
176 | bar: [
|
177 | "baz",
|
178 | "quux",
|
179 |
|
180 | // The property of the array
|
181 | [Symbol.for('after-value:0')]: [{
|
182 | type: 'LineComment',
|
183 | value: ' after-value:0',
|
184 | inline: true,
|
185 | loc: ...
|
186 | }, ...],
|
187 | ...
|
188 | ]
|
189 | }
|
190 | ```
|
191 |
|
192 | There are **EIGHT** kinds of symbol properties:
|
193 |
|
194 | ```js
|
195 | // Comments before everything
|
196 | Symbol.for('before-all')
|
197 |
|
198 | // If all things inside an object or an array are comments
|
199 | Symbol.for('before')
|
200 |
|
201 | // comment tokens before
|
202 | // - a property of an object
|
203 | // - an item of an array
|
204 | // and after the previous comma(`,`) or the opening bracket(`{` or `[`)
|
205 | Symbol.for(`before:${prop}`)
|
206 |
|
207 | // comment tokens after property key `prop` and before colon(`:`)
|
208 | Symbol.for(`after-prop:${prop}`)
|
209 |
|
210 | // comment tokens after the colon(`:`) of property `prop` and before property value
|
211 | Symbol.for(`after-colon:${prop}`)
|
212 |
|
213 | // comment tokens after
|
214 | // - the value of property `prop` inside an object
|
215 | // - the item of index `prop` inside an array
|
216 | // and before the next key-value/item delimiter(`,`)
|
217 | // or the closing bracket(`}` or `]`)
|
218 | Symbol.for(`after-value:${prop}`)
|
219 |
|
220 | // comment tokens after
|
221 | // - comma(`,`)
|
222 | // - the value of property `prop` if it is the last property
|
223 | Symbol.for(`after:${prop}`)
|
224 |
|
225 | // Comments after everything
|
226 | Symbol.for('after-all')
|
227 | ```
|
228 |
|
229 | And the value of each symbol property is an **array** of `CommentToken`
|
230 |
|
231 | ```ts
|
232 | interface CommentToken {
|
233 | type: 'BlockComment' | 'LineComment'
|
234 | // The content of the comment, including whitespaces and line breaks
|
235 | value: string
|
236 | // If the start location is the same line as the previous token,
|
237 | // then `inline` is `true`
|
238 | inline: boolean
|
239 |
|
240 | // But pay attention that,
|
241 | // locations will NOT be maintained when stringified
|
242 | loc: CommentLocation
|
243 | }
|
244 |
|
245 | interface CommentLocation {
|
246 | // The start location begins at the `//` or `/*` symbol
|
247 | start: Location
|
248 | // The end location of multi-line comment ends at the `*/` symbol
|
249 | end: Location
|
250 | }
|
251 |
|
252 | interface Location {
|
253 | line: number
|
254 | column: number
|
255 | }
|
256 | ```
|
257 |
|
258 | ### Parse into an object without comments
|
259 |
|
260 | ```js
|
261 | console.log(parse(content, null, true))
|
262 | ```
|
263 |
|
264 | And the result will be:
|
265 |
|
266 | ```js
|
267 | {
|
268 | foo: 1,
|
269 | bar: [
|
270 | "baz",
|
271 | "quux"
|
272 | ]
|
273 | }
|
274 | ```
|
275 |
|
276 | ### Special cases
|
277 |
|
278 | ```js
|
279 | const parsed = parse(`
|
280 | // comment
|
281 | 1
|
282 | `)
|
283 |
|
284 | console.log(parsed === 1)
|
285 | // false
|
286 | ```
|
287 |
|
288 | If we parse a JSON of primative type with `remove_comments:false`, then the return value of `parse()` will be of object type.
|
289 |
|
290 | The value of `parsed` is equivalent to:
|
291 |
|
292 | ```js
|
293 | const parsed = new Number(1)
|
294 |
|
295 | parsed[Symbol.for('before-all')] = [{
|
296 | type: 'LineComment',
|
297 | value: ' comment',
|
298 | inline: false,
|
299 | loc: ...
|
300 | }]
|
301 | ```
|
302 |
|
303 | Which is similar for:
|
304 |
|
305 | - `Boolean` type
|
306 | - `String` type
|
307 |
|
308 | For example
|
309 |
|
310 | ```js
|
311 | const parsed = parse(`
|
312 | "foo" /* comment */
|
313 | `)
|
314 | ```
|
315 |
|
316 | Which is equivalent to
|
317 |
|
318 | ```js
|
319 | const parsed = new String('foo')
|
320 |
|
321 | parsed[Symbol.for('after-all')] = [{
|
322 | type: 'BlockComment',
|
323 | value: ' comment ',
|
324 | inline: true,
|
325 | loc: ...
|
326 | }]
|
327 | ```
|
328 |
|
329 | But there is one exception:
|
330 |
|
331 | ```js
|
332 | const parsed = parse(`
|
333 | // comment
|
334 | null
|
335 | `)
|
336 |
|
337 | console.log(parsed === null) // true
|
338 | ```
|
339 |
|
340 | ## stringify()
|
341 |
|
342 | ```ts
|
343 | stringify(object: any, replacer?, space?): string
|
344 | ```
|
345 |
|
346 | The arguments are the same as the vanilla [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
|
347 |
|
348 | And it does the similar thing as the vanilla one, but also deal with extra properties and convert them into comments.
|
349 |
|
350 | ```js
|
351 | console.log(stringify(parsed, null, 2))
|
352 | // Exactly the same as `content`
|
353 | ```
|
354 |
|
355 | #### space
|
356 |
|
357 | If space is not specified, or the space is an empty string, the result of `stringify()` will have no comments.
|
358 |
|
359 | For the case above:
|
360 |
|
361 | ```js
|
362 | console.log(stringify(result)) // {"a":1}
|
363 | console.log(stringify(result, null, 2)) // is the same as `code`
|
364 | ```
|
365 |
|
366 | ## assign(target: object, source?: object, keys?: Array<string>)
|
367 |
|
368 | - **target** `object` the target object
|
369 | - **source?** `object` the source object. This parameter is optional but it is silly to not pass this argument.
|
370 | - **keys?** `Array<string>` If not specified, all enumerable own properties of `source` will be used.
|
371 |
|
372 | This method is used to copy the enumerable own properties and their corresponding comment symbol properties to the target object.
|
373 |
|
374 | ```js
|
375 | const parsed = parse(`{
|
376 | // This is a comment
|
377 | "foo": "bar"
|
378 | }`)
|
379 |
|
380 | const obj = assign({
|
381 | bar: 'baz'
|
382 | }, parsed)
|
383 |
|
384 | stringify(obj, null, 2)
|
385 | // {
|
386 | // "bar": "baz",
|
387 | // // This is a comment
|
388 | // "foo": "bar"
|
389 | // }
|
390 | ```
|
391 |
|
392 | ## `CommentArray`
|
393 |
|
394 | > Advanced Section
|
395 |
|
396 | All arrays of the parsed object are `CommentArray`s.
|
397 |
|
398 | The constructor of `CommentArray` could be accessed by:
|
399 |
|
400 | ```js
|
401 | const {CommentArray} = require('comment-json')
|
402 | ```
|
403 |
|
404 | If we modify a comment array, its comment symbol properties could be handled automatically.
|
405 |
|
406 | ```js
|
407 | const parsed = parse(`{
|
408 | "foo": [
|
409 | // bar
|
410 | "bar",
|
411 | // baz,
|
412 | "baz"
|
413 | ]
|
414 | }`)
|
415 |
|
416 | parsed.foo.unshift('qux')
|
417 |
|
418 | stringify(parsed, null, 2)
|
419 | // {
|
420 | // "foo": [
|
421 | // "qux",
|
422 | // // bar
|
423 | // "bar",
|
424 | // // baz
|
425 | // "baz"
|
426 | // ]
|
427 | // }
|
428 | ```
|
429 |
|
430 | Oh yeah! 😆
|
431 |
|
432 | But pay attention, if you reassign the property of a comment array with a normal array, all comments will be gone:
|
433 |
|
434 | ```js
|
435 | parsed.foo = ['quux'].concat(parsed.foo)
|
436 | stringify(parsed, null, 2)
|
437 | // {
|
438 | // "foo": [
|
439 | // "quux",
|
440 | // "qux",
|
441 | // "bar",
|
442 | // "baz"
|
443 | // ]
|
444 | // }
|
445 |
|
446 | // Whoooops!! 😩 Comments are gone
|
447 | ```
|
448 |
|
449 | Instead, we should:
|
450 |
|
451 | ```js
|
452 | parsed.foo = new CommentArray('quux').concat(parsed.foo)
|
453 | stringify(parsed, null, 2)
|
454 | // {
|
455 | // "foo": [
|
456 | // "quux",
|
457 | // "qux",
|
458 | // // bar
|
459 | // "bar",
|
460 | // // baz
|
461 | // "baz"
|
462 | // ]
|
463 | // }
|
464 | ```
|
465 |
|
466 | ## Special Cases about Trailing Comma
|
467 |
|
468 | If we have a JSON string `str`
|
469 |
|
470 | ```js
|
471 | {
|
472 | "foo": "bar", // comment
|
473 | }
|
474 | ```
|
475 |
|
476 | ```js
|
477 | // When stringify, trailing commas will be eliminated
|
478 | const stringified = stringify(parse(str), null, 2)
|
479 | console.log(stringified)
|
480 | ```
|
481 |
|
482 | And it will print:
|
483 |
|
484 | ```js
|
485 | {
|
486 | "foo": "bar" // comment
|
487 | }
|
488 | ```
|
489 |
|
490 | ## License
|
491 |
|
492 | [MIT](LICENSE)
|
493 |
|
494 | ## Change Logs
|
495 |
|
496 | See [releases](https://github.com/kaelzhang/node-comment-json/releases)
|