UNPKG

11.6 kBMarkdownView Raw
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<!-- optional appveyor tst
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<!-- optional npm version
7[![NPM version](https://badge.fury.io/js/comment-json.svg)](http://badge.fury.io/js/comment-json)
8-->
9<!-- optional npm downloads
10[![npm module downloads per month](http://img.shields.io/npm/dm/comment-json.svg)](https://www.npmjs.org/package/comment-json)
11-->
12<!-- optional dependency status
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
18Parse 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
25The 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
29There 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
31Imagine 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
33So, **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
39For 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
47For TypeScript developers, [`@types/comment-json`](https://www.npmjs.com/package/@types/comment-json) could be used.
48
49## Usage
50
51package.json:
52
53```js
54{
55 // package name
56 "name": "comment-json"
57}
58```
59
60```js
61const {
62 parse,
63 stringify,
64 assign
65} = require('comment-json')
66const fs = require('fs')
67
68const obj = parse(fs.readFileSync('package.json').toString())
69
70console.log(obj.name) // comment-json
71
72stringify(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
80parse(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
88Returns `object | string | number | boolean | null` corresponding to the given JSON text.
89
90If 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
119const {inspect} = require('util')
120
121const parsed = parse(content)
122
123console.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
131console.log(Object.keys(parsed))
132// > ['foo', 'bar']
133
134console.log(stringify(parsed, null, 2))
135// 🚀 Exact as the content above! 🚀
136```
137
138And 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
192There are **EIGHT** kinds of symbol properties:
193
194```js
195// Comments before everything
196Symbol.for('before-all')
197
198// If all things inside an object or an array are comments
199Symbol.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 `[`)
205Symbol.for(`before:${prop}`)
206
207// comment tokens after property key `prop` and before colon(`:`)
208Symbol.for(`after-prop:${prop}`)
209
210// comment tokens after the colon(`:`) of property `prop` and before property value
211Symbol.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 `]`)
218Symbol.for(`after-value:${prop}`)
219
220// comment tokens after
221// - comma(`,`)
222// - the value of property `prop` if it is the last property
223Symbol.for(`after:${prop}`)
224
225// Comments after everything
226Symbol.for('after-all')
227```
228
229And the value of each symbol property is an **array** of `CommentToken`
230
231```ts
232interface 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
245interface 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
252interface Location {
253 line: number
254 column: number
255}
256```
257
258### Parse into an object without comments
259
260```js
261console.log(parse(content, null, true))
262```
263
264And 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
279const parsed = parse(`
280// comment
2811
282`)
283
284console.log(parsed === 1)
285// false
286```
287
288If we parse a JSON of primative type with `remove_comments:false`, then the return value of `parse()` will be of object type.
289
290The value of `parsed` is equivalent to:
291
292```js
293const parsed = new Number(1)
294
295parsed[Symbol.for('before-all')] = [{
296 type: 'LineComment',
297 value: ' comment',
298 inline: false,
299 loc: ...
300}]
301```
302
303Which is similar for:
304
305- `Boolean` type
306- `String` type
307
308For example
309
310```js
311const parsed = parse(`
312"foo" /* comment */
313`)
314```
315
316Which is equivalent to
317
318```js
319const parsed = new String('foo')
320
321parsed[Symbol.for('after-all')] = [{
322 type: 'BlockComment',
323 value: ' comment ',
324 inline: true,
325 loc: ...
326}]
327```
328
329But there is one exception:
330
331```js
332const parsed = parse(`
333// comment
334null
335`)
336
337console.log(parsed === null) // true
338```
339
340## stringify()
341
342```ts
343stringify(object: any, replacer?, space?): string
344```
345
346The arguments are the same as the vanilla [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
347
348And it does the similar thing as the vanilla one, but also deal with extra properties and convert them into comments.
349
350```js
351console.log(stringify(parsed, null, 2))
352// Exactly the same as `content`
353```
354
355#### space
356
357If space is not specified, or the space is an empty string, the result of `stringify()` will have no comments.
358
359For the case above:
360
361```js
362console.log(stringify(result)) // {"a":1}
363console.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
372This method is used to copy the enumerable own properties and their corresponding comment symbol properties to the target object.
373
374```js
375const parsed = parse(`{
376 // This is a comment
377 "foo": "bar"
378}`)
379
380const obj = assign({
381 bar: 'baz'
382}, parsed)
383
384stringify(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
396All arrays of the parsed object are `CommentArray`s.
397
398The constructor of `CommentArray` could be accessed by:
399
400```js
401const {CommentArray} = require('comment-json')
402```
403
404If we modify a comment array, its comment symbol properties could be handled automatically.
405
406```js
407const parsed = parse(`{
408 "foo": [
409 // bar
410 "bar",
411 // baz,
412 "baz"
413 ]
414}`)
415
416parsed.foo.unshift('qux')
417
418stringify(parsed, null, 2)
419// {
420// "foo": [
421// "qux",
422// // bar
423// "bar",
424// // baz
425// "baz"
426// ]
427// }
428```
429
430Oh yeah! 😆
431
432But pay attention, if you reassign the property of a comment array with a normal array, all comments will be gone:
433
434```js
435parsed.foo = ['quux'].concat(parsed.foo)
436stringify(parsed, null, 2)
437// {
438// "foo": [
439// "quux",
440// "qux",
441// "bar",
442// "baz"
443// ]
444// }
445
446// Whoooops!! 😩 Comments are gone
447```
448
449Instead, we should:
450
451```js
452parsed.foo = new CommentArray('quux').concat(parsed.foo)
453stringify(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
468If 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
478const stringified = stringify(parse(str), null, 2)
479console.log(stringified)
480```
481
482And 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
496See [releases](https://github.com/kaelzhang/node-comment-json/releases)