comment-json

repository·master·Indexed 41 days ago

https://github.com/kaelzhang/node-comment-json

A library for parsing and stringifying JSON with comments, preserving formatting and user-written documentation. Version 5.0.0 provides functions like parse(), stringify(), and assign(), and utilizes a specialized CommentArray class to maintain comments during array mutations. It stores comments as Symbol properties on objects and supports the TC39 proposal-json-parse-with-source for handling BigInt values.

Tokens
3.9K
Snippets
15
Records
16
Agent score
2%

What's inside comment-json

  1. How CommentArray works and how to use it

    master

    All arrays within a parsed object are instances of CommentArray. These special arrays automatically handle comment symbol properties when modified using standard array methods like .unshift(), .push(), etc.

    Warning: If you reassign a property that was a CommentArray to a standard JavaScript array, all associated comments will be lost. To maintain comments when creating new arrays, you must wrap the new data in a new CommentArray().

    const {parse, stringify, CommentArray} = require('comment-json')
    
    const parsed = parse(`{
      "foo": [
        // bar
        "bar",
        // baz,
        "baz"
      ]
    }`)
    
    // Correct way to reassign while preserving comments:
    parsed.foo = new CommentArray('quux').concat(parsed.foo)
    
    stringify(parsed, null, 2)
    // {
    //   "foo": [
    //     "quux",
    //     // bar
    //     "bar",
    //     // baz
    //     "baz"
    //   ]
    // }
  2. How comments are stored as Symbol properties

    master

    When parse() is called, comments are stored as arrays of CommentToken objects attached to the parsed structure using Symbol.for() keys. This allows you to access the data normally while keeping metadata attached.

    Available Symbol Keys:

    • Symbol.for('before-all'): Comments before the entire JSON structure.
    • Symbol.for('before'): Comments inside an object/array before any members.
    • Symbol.for('before:${prop}'): Comments before a property/item (after the previous delimiter).
    • Symbol.for('after-prop:${prop}'): Comments after a property key but before the colon.
    • Symbol.for('after-colon:${prop}'): Comments after the colon but before the value.
    • Symbol.for('after-value:${prop}'): Comments after a value/item but before the next delimiter.
    • Symbol.for('after:${prop}'): Comments after the last property/item in a block.
    • Symbol.for('after'): Inner end of an object/array (used for stringification).
    • Symbol.for('after-all'): Comments after the entire JSON structure.

    CommentToken Structure: Each symbol points to an array of tokens:

    • BlankLineToken: { type: 'BlankLine', inline: false }
    • CommentLineToken: { type: 'BlockComment' | 'LineComment', value: string, inline: boolean, loc?: CommentLocation }
  3. Handling BigInts with parse() and stringify()

    master

    The library implements the TC39 proposal-json-parse-with-source. This allows you to use a reviver function in parse() that receives the original string source as a third argument, which is useful for parsing BigInt values.

    When using stringify(), you can handle BigInt values by using JSON.rawJSON() (available in Node.js >= 21) to ensure they are written correctly to the output string.

    const {parse, stringify} = require('comment-json')
    
    // Parsing BigInts using the source argument
    const parsed = parse(
      `{"foo": 9007199254740993}`,
      (key, value, {source}) =>
        /^[0-9]+$/.test(source) ? BigInt(source) : value
    )
    
    // Stringifying BigInts using JSON.rawJSON
    stringify(parsed, (key, val) =>
      typeof val === 'bigint'
        ? JSON.rawJSON(String(val))
        : val
    )
    // {"foo": 9007199254740993}
  4. Use CommentArray to preserve comments during array mutations

    master

    CommentArray is a specialized subclass of the native Array class designed to preserve JSON comments when performing common array operations. It automatically handles the repositioning or removal of comments stored as symbol properties when the array structure changes via methods like splice, slice, push, pop, unshift, shift, reverse, concat, or sort.

    When you use parse() from the library on a JSON string containing comments, the resulting array will be an instance of CommentArray.

    Key Behaviors

    • Mutation Methods: Methods like splice, unshift, shift, and pop update the internal comment mapping so that comments stay attached to their original data elements.
    • Non-Mutating Methods: Methods like slice and concat return new CommentArray instances with the appropriate comments copied over.
    • Sorting: The sort method ensures that comments move along with the elements being reordered.
    // Assuming 'parse' returns a CommentArray
    const arr = parse('[1, // comment\n 2, // comment\n 3]') 
    
    // arr is a CommentArray instance
    // Mutating the array preserves the comments attached to the elements
    arr.splice(1, 1) 
    // The comment originally at index 1 is now correctly repositioned
  5. Basic usage: Parse and stringify JSON with comments

    master

    Use parse to convert a JSON string with comments into a JavaScript object. The comments are preserved as Symbol properties on the object. Use stringify to convert the object back into a JSON string, which will include the original comments.

    const { parse, stringify } = require('comment-json')
    const fs = require('fs')
    
    // Parse a file containing comments
    const obj = parse(fs.readFileSync('package.json').toString())
    
    console.log(obj.name) // Access standard properties normally
    
    // Stringify back to a string, preserving comments
    const output = stringify(obj, null, 2)
  6. Sort JSON keys while preserving comments

    master

    To sort the keys of a JSON file without losing comments, use parse to load the object, then use assign to copy properties into a new object following a specific key order (e.g., from Object.keys().sort()).

    const { parse, assign, stringify } = require('comment-json')
    
    const parsed = parse(`{
      // b
      "b": 2,
      // a
      "a": 1
    }`)
    
    // Copy properties including comments to a new object in sorted order
    const sorted = assign(
      {},
      parsed,
      Object.keys(parsed).sort()
    )
    
    console.log(stringify(sorted, null, 2))
    // {
    //   // a
    //   "a": 1,
    //   // b
    //   "b": 2
    // }
  7. Use parse() to load JSON with comments

    master

    The parse function converts a JSON string into a JavaScript object. It supports comments everywhere, including inside arrays.

    Signature: parse(text, reviver?, options?)

    • text (string): The JSON string to parse.
    • reviver? (Function | null): A function that transforms the value before returning. It receives a third argument context (useful for BigInt parsing).
    • options? (boolean | object):
      • true: Shorthand for { no_comments: true }.
      • no_comments (boolean): If true, LineComment and BlockComment tokens are not maintained.
      • no_blank_lines (boolean): If true, BlankLine tokens are not generated.

    Returns: object | string | number | boolean | null (a CommentJSONValue).

    parse(
      text,
      reviver? = null,
      options? = false | {
        no_comments?: boolean,
        no_blank_lines?: boolean
      }
    ): object | string | number | boolean | null
  8. Use stringify() to convert objects to JSON strings

    master

    The stringify() method converts an object back into a JSON string. It behaves similarly to the vanilla JSON.stringify(), but it also handles extra properties used to store comments and converts them into actual JSON comments.

    Note on spacing: If the space argument is not specified or is an empty string, the resulting string will contain no comments. To preserve comments and formatting, provide a space value (e.g., 2).

    stringify(object: any, replacer?, space?): string
    // Preserving comments with indentation
    console.log(stringify(parsed, null, 2))
    
    // No comments if space is not specified
    console.log(stringify(result)) // {"a":1}
  9. Query comments in TypeScript

    master

    To query comments in a TypeScript environment, use the CommentDescriptor type to define the symbol name and cast the result of Symbol.for() to CommentSymbol to enable type detection.

    import {
      CommentDescriptor, 
      CommentSymbol, 
      parse, 
      CommentArray
    } from 'comment-json'
    
    const parsed = parse(`{ /* test */ "foo": "bar" }`)
    // Use CommentDescriptor to ensure properly formatted symbol names
    const symbolName: CommentDescriptor = 'before:foo'
    
    // Casting to CommentSymbol is mandatory for TS to detect the query
    const comment = (parsed as CommentArray<string>)[Symbol.for(symbolName) as CommentSymbol][0].value
  10. Use removeComments() to delete specific comments

    master

    The removeComments() method removes comments from a specific location within an object.

    Parameters:

    • target: The object to remove comments from.
    • location: An object specifying where (a CommentPrefix) and optionally key (for property-specific comments).
    const {parse, stringify, removeComments} = require('comment-json')
    
    const obj = parse(`{
      // comment before foo
      "foo": 1, // comment after foo
      "bar": 2
    }`)
    
    // Remove comment before 'foo'
    removeComments(obj, { where: 'before', key: 'foo' })
    
    console.log(stringify(obj, null, 2))
    // {
    //   "foo": 1, // comment after foo
    //   "bar": 2
    // }
  11. Use moveComments() to relocate comments

    master

    The moveComments() method allows you to move comments from one location to another within objects or between different objects.

    Parameters:

    • source: The object containing the comments to move.
    • target: The destination object (defaults to source if not provided).
    • from: The source location. Requires where (a CommentPrefix) and optionally key (for property-specific comments).
    • to: The destination location. Requires where and optionally key.
    • override: A boolean (defaults to false). If true, existing comments at the target location are replaced. If false, comments are appended.
    const {parse, stringify, moveComments} = require('comment-json')
    
    const obj = parse(`{
      "foo": 1, // comment after foo
      "bar": 2
    }`)
    
    // Move comment from `after 'foo'` to `after` (the object level)
    moveComments(obj, obj,
      { where: 'after', key: 'foo' },
      { where: 'after' }
    )
    
    obj.baz = 3
    console.log(stringify(obj, null, 2))
    // {
    //   "foo": 1,
    //   "bar": 2,
    //   "baz": 3
    // // comment after foo
    // }