Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
comment-json
Advanced tools
Parse and stringify JSON with comments. It will retain comments even after saved!
The comment-json npm package allows you to parse, modify, and stringify JSON files while preserving comments. This is particularly useful for configuration files where comments are often used to provide context or instructions.
Parsing JSON with comments
This feature allows you to parse JSON strings that include comments. The comments are ignored in the resulting JavaScript object.
const commentJson = require('comment-json');
const jsonWithComments = `{
// This is a comment
"key": "value"
}`;
const parsed = commentJson.parse(jsonWithComments);
console.log(parsed); // { key: 'value' }
Stringifying JSON with comments
This feature allows you to convert a JavaScript object back into a JSON string, optionally including comments.
const commentJson = require('comment-json');
const obj = { key: 'value' };
const jsonString = commentJson.stringify(obj, null, 2);
console.log(jsonString); // {
// This is a comment
"key": "value"
}
Modifying JSON while preserving comments
This feature allows you to modify a parsed JSON object and then convert it back to a JSON string, preserving the original comments.
const commentJson = require('comment-json');
const jsonWithComments = `{
// This is a comment
"key": "value"
}`;
let parsed = commentJson.parse(jsonWithComments);
parsed.newKey = 'newValue';
const modifiedJsonString = commentJson.stringify(parsed, null, 2);
console.log(modifiedJsonString); // {
// This is a comment
"key": "value",
"newKey": "newValue"
}
JSON5 is a JSON extension that aims to make JSON more human-friendly. It allows comments, trailing commas, and more. However, unlike comment-json, JSON5 does not preserve comments when parsing and stringifying.
Hjson is a user interface for JSON. It allows comments and is designed to be easy to read and write. Similar to JSON5, Hjson does not preserve comments when converting between JSON and JavaScript objects.
The jsonc-parser package is used by Visual Studio Code to handle JSON with comments. It can parse JSON with comments and provide a syntax tree, but it is more complex to use compared to comment-json.
Parse and stringify JSON with comments. It will retain comments even after saved!
The usage of comment-json
is exactly the same as the vanilla JSON
object.
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!
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.
$ 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
.
package.json:
{
// package name
"name": "comment-json"
}
const {
parse,
stringify,
assign
} = require('comment-json')
const fs = require('fs')
const obj = parse(fs.readFileSync('package.json').toString())
console.log(obj.name) // comment-json
stringify(obj, null, 2)
// Will be the same as package.json, Oh yeah! 😆
// which will be very useful if we use a json file to store configurations.
It is a common use case to sort the keys of a JSON file
const parsed = parse(`{
// b
"b": 2
// a
"a": 1
}`)
// Copy the properties including comments from `parsed` to the new object `{}`
// according to the sequence of the given keys
const sorted = assign(
{},
parsed,
// You could also use your custom sorting function
Object.keys(parsed).sort()
)
console.log(stringify(sorted, null, 2))
// {
// // a
// "a": 1,
// // b
// "b": 2
// }
For details about assign
, see here.
parse(text, reviver? = null, remove_comments? = false)
: object | string | number | boolean | null
string
The string to parse as JSON. See the JSON object for a description of JSON syntax.Function() | null
Default to null
. It acts the same as the second parameter of JSON.parse
. If a function, prescribes how the value originally produced by parsing is transformed, before being returned.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:
/**
before-all
*/
// before-all
{ // before:foo
// before:foo
/* before:foo */
"foo" /* after-prop:foo */: // after-colon:foo
1 // after-value:foo
// after-value:foo
, // after:foo
// before:bar
"bar": [ // before:0
// before:0
"baz" // after-value:0
// after-value:0
, // after:0
"quux"
// after:1
] // after:bar
// after:bar
}
// after-all
const {inspect} = require('util')
const parsed = parse(content)
console.log(
inspect(parsed, {
// Since 4.0.0, symbol properties of comments are not enumerable,
// use `showHidden: true` to print them
showHidden: true
})
)
console.log(Object.keys(parsed))
// > ['foo', 'bar']
console.log(stringify(parsed, null, 2))
// 🚀 Exact as the content above! 🚀
And the value of parsed
will be:
{
// Comments before the JSON object
[Symbol.for('before-all')]: [{
type: 'BlockComment',
value: '\n before-all\n ',
inline: false,
loc: {
// The start location of `/**`
start: {
line: 1,
column: 0
},
// The end location of `*/`
end: {
line: 3,
column: 3
}
}
}, {
type: 'LineComment',
value: ' before-all',
inline: false,
loc: ...
}],
...
[Symbol.for('after-prop:foo')]: [{
type: 'BlockComment',
value: ' after-prop:foo ',
inline: true,
loc: ...
}],
// The real value
foo: 1,
bar: [
"baz",
"quux",
// The property of the array
[Symbol.for('after-value:0')]: [{
type: 'LineComment',
value: ' after-value:0',
inline: true,
loc: ...
}, ...],
...
]
}
There are EIGHT kinds of symbol properties:
// Comments before everything
Symbol.for('before-all')
// If all things inside an object or an array are comments
Symbol.for('before')
// comment tokens before
// - a property of an object
// - an item of an array
// and after the previous comma(`,`) or the opening bracket(`{` or `[`)
Symbol.for(`before:${prop}`)
// comment tokens after property key `prop` and before colon(`:`)
Symbol.for(`after-prop:${prop}`)
// comment tokens after the colon(`:`) of property `prop` and before property value
Symbol.for(`after-colon:${prop}`)
// comment tokens after
// - the value of property `prop` inside an object
// - the item of index `prop` inside an array
// and before the next key-value/item delimiter(`,`)
// or the closing bracket(`}` or `]`)
Symbol.for(`after-value:${prop}`)
// comment tokens after
// - comma(`,`)
// - the value of property `prop` if it is the last property
Symbol.for(`after:${prop}`)
// Comments after everything
Symbol.for('after-all')
And the value of each symbol property is an array of CommentToken
interface CommentToken {
type: 'BlockComment' | 'LineComment'
// The content of the comment, including whitespaces and line breaks
value: string
// If the start location is the same line as the previous token,
// then `inline` is `true`
inline: boolean
// But pay attention that,
// locations will NOT be maintained when stringified
loc: CommentLocation
}
interface CommentLocation {
// The start location begins at the `//` or `/*` symbol
start: Location
// The end location of multi-line comment ends at the `*/` symbol
end: Location
}
interface Location {
line: number
column: number
}
console.log(parse(content, null, true))
And the result will be:
{
foo: 1,
bar: [
"baz",
"quux"
]
}
const parsed = parse(`
// comment
1
`)
console.log(parsed === 1)
// 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:
const parsed = new Number(1)
parsed[Symbol.for('before-all')] = [{
type: 'LineComment',
value: ' comment',
inline: false,
loc: ...
}]
Which is similar for:
Boolean
typeString
typeFor example
const parsed = parse(`
"foo" /* comment */
`)
Which is equivalent to
const parsed = new String('foo')
parsed[Symbol.for('after-all')] = [{
type: 'BlockComment',
value: ' comment ',
inline: true,
loc: ...
}]
But there is one exception:
const parsed = parse(`
// comment
null
`)
console.log(parsed === null) // true
stringify(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.
console.log(stringify(parsed, null, 2))
// Exactly the same as `content`
If space is not specified, or the space is an empty string, the result of stringify()
will have no comments.
For the case above:
console.log(stringify(result)) // {"a":1}
console.log(stringify(result, null, 2)) // is the same as `code`
object
the target objectobject
the source object. This parameter is optional but it is silly to not pass this argument.Array<string>
If not specified, all enumerable own properties of source
will be used.This method is used to copy the enumerable own properties and their corresponding comment symbol properties to the target object.
const parsed = parse(`// before all
{
// This is a comment
"foo": "bar"
}`)
const obj = assign({
bar: 'baz'
}, parsed)
stringify(obj, null, 2)
// // before all
// {
// "bar": "baz",
// // This is a comment
// "foo": "bar"
// }
keys
But if argument keys
is specified and is not empty, then comment before all
, which belongs to no properties, will NOT be copied.
const obj = assign({
bar: 'baz'
}, parsed, ['foo'])
stringify(obj, null, 2)
// {
// "bar": "baz",
// // This is a comment
// "foo": "bar"
// }
Specifying the argument keys
as an empty array indicates that it will only copy non-property symbols of comments
const obj = assign({
bar: 'baz'
}, parsed, [])
stringify(obj, null, 2)
// // before all
// {
// "bar": "baz",
// }
Non-property symbols include:
Symbol.for('before-all')
Symbol.for('before')
Symbol.for('after-all')
CommentArray
Advanced Section
All arrays of the parsed object are CommentArray
s.
The constructor of CommentArray
could be accessed by:
const {CommentArray} = require('comment-json')
If we modify a comment array, its comment symbol properties could be handled automatically.
const parsed = parse(`{
"foo": [
// bar
"bar",
// baz,
"baz"
]
}`)
parsed.foo.unshift('qux')
stringify(parsed, null, 2)
// {
// "foo": [
// "qux",
// // bar
// "bar",
// // baz
// "baz"
// ]
// }
Oh yeah! 😆
But pay attention, if you reassign the property of a comment array with a normal array, all comments will be gone:
parsed.foo = ['quux'].concat(parsed.foo)
stringify(parsed, null, 2)
// {
// "foo": [
// "quux",
// "qux",
// "bar",
// "baz"
// ]
// }
// Whoooops!! 😩 Comments are gone
Instead, we should:
parsed.foo = new CommentArray('quux').concat(parsed.foo)
stringify(parsed, null, 2)
// {
// "foo": [
// "quux",
// "qux",
// // bar
// "bar",
// // baz
// "baz"
// ]
// }
If we have a JSON string str
{
"foo": "bar", // comment
}
// When stringify, trailing commas will be eliminated
const stringified = stringify(parse(str), null, 2)
console.log(stringified)
And it will print:
{
"foo": "bar" // comment
}
See releases
FAQs
Parse and stringify JSON with comments. It will retain comments even after saved!
The npm package comment-json receives a total of 1,445,011 weekly downloads. As such, comment-json popularity was classified as popular.
We found that comment-json demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.