npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@graysonlang/rjson

v1.2.0

Published

Relaxed JSON parser for standard JSON plus comments and trailing commas.

Readme

Relaxed JSON

@graysonlang/rjson parses Relaxed JSON: standard JSON values with comments and trailing commas accepted before native JSON.parse validation.

It is intentionally not JSON5. Object keys still need double quotes, strings still need double quotes, numbers are still JSON numbers, and values are still true, false, and null.

Install

npm install @graysonlang/rjson

Usage

import { parse, toJson } from '@graysonlang/rjson';

const data = parse(`
{
  // Comments are allowed.
  "name": "lamp",
  "words": ["lamp", "lantern",],
}
`);

console.log(data.name);
console.log(toJson('{ "ok": true, }'));

Comments

Both JavaScript comment styles are accepted anywhere a token boundary is legal.

parse(`{
  // A line comment on its own line.
  "name": "demo",

  /* A block comment. */
  "enabled": true, // Or at the end of a line.

  /*
    A block comment can span
    as many lines as you like.
  */
  "items": ["red", "blue"],

  "value": /* even between a key and its value */ 42
}`);

Mixing the two styles

Whichever comment opens first runs to its own terminator. The other style's markers inside it are just text.

// A // inside a block comment does not close it early.
parse('{ /* contains // marker */ "a": 1 }');           // -> { a: 1 }

// A /* or */ inside a line comment opens and closes nothing.
parse('{ // contains /* and */ markers\n"a": 1 }');     // -> { a: 1 }

// So a line comment still ends at the newline, not at some later */.
parse('{ "a": 1 // /* never opened\n, "b": 2 }');       // -> { a: 1, b: 2 }

Block comments of the same style do not nest. The first */ closes the comment, and whatever follows has to be valid JSON on its own:

parse('{ /* outer /* inner */ "a": 1 }');   // -> { a: 1 }
parse('{ /* outer /* inner */ */ "a": 1 }'); // SyntaxError: the stray */ is not JSON

Comments and strings

Comment markers inside a string are data. Strings are tracked through the scan, including escapes, so nothing inside quotes is ever treated as a comment.

parse('{ "a": "// not a comment" }');            // -> { a: '// not a comment' }
parse('{ "a": "/* also not */" }');              // -> { a: '/* also not */' }

// An escaped quote does not end the string.
parse('{ "a": "esc \\" // still string" }');     // -> { a: 'esc " // still string' }

// An escaped backslash does end it, so the next quote is real.
parse('{ "a": "back \\\\", "b": 1 }');           // -> { a: 'back \\', b: 1 }

// Escape forms that spell comment markers stay inside the string.
parse('{ "a": "\\/\\/ escaped solidus" }');      // -> { a: '// escaped solidus' }
parse('{ "a": "\\u002f\\u002f unicode" }');      // -> { a: '// unicode' }

The reverse holds too: a quote inside a comment cannot open a string.

parse('{ /* "quoted */ "a": 1 }');               // -> { a: 1 }
parse('{ "a": 1 // "unclosed\n, "b": 2 }');      // -> { a: 1, b: 2 }

Trailing commas

A comma directly before } or ] is dropped, including when a comment sits between them.

parse('[1, 2, 3,]');                  // -> [1, 2, 3]
parse('{ "a": 1, }');                 // -> { a: 1 }
parse('{ "a": 1, /* between */ }');   // -> { a: 1 }
parse('{ "a": 1, // between\n}');     // -> { a: 1 }

A comma only ever trails a value, so these stay errors:

parse('[,]');      // SyntaxError
parse('{,}');      // SyntaxError
parse('[1,,]');    // SyntaxError
parse('[1,,2]');   // SyntaxError

Output

toJson returns clean strict JSON. Comments and trailing commas are deleted, whitespace they orphan at the end of a line is trimmed, and a line that held nothing but a comment is dropped. Lines no removal touched are passed through byte for byte, so your formatting survives.

toJson('{\n  // comment only\n  "a": 1,\n}');
// '{\n  "a": 1\n}'

Pass preserveOffsets when you would rather keep character positions than get tidy text. Every removed character becomes a space, so the result has the same length as the input and each remaining character keeps its original offset. That makes native JSON.parse diagnostics point at the right place in the original source.

toJson('{ "a": 1, /* c */ }', { preserveOffsets: true });
// '{ "a": 1          }'   <- same length as the input

parse uses preserveOffsets internally for exactly that reason. Comments are not preserved for round-trip editing in either mode.

Still rejected

  • unquoted object keys
  • single-quoted strings
  • undefined, NaN, and Infinity
  • hex numbers
  • JavaScript expressions
  • nested block comments of the same style
  • leading or doubled commas
  • macros, imports, and include directives

Relation to JSONC and JSON5

JSONC is JSON with comments, the dialect VS Code uses for settings.json and TypeScript uses for tsconfig.json. It has no formal specification; the reference implementation is jsonc-parser.

rjson accepts the same language as JSONC with trailing commas enabled. Across 78 inputs covering both comment styles, comments interacting with strings and escapes, trailing and stray commas, number and string forms, and malformed input, no difference in accept or reject, or in the parsed value, was found against jsonc-parser 3.3.1 run with allowTrailingComma: true.

The differences are in behaviour rather than in what is accepted:

  • Trailing commas are always accepted here. In jsonc-parser they are opt-in through allowTrailingComma, which is off by default, so its out-of-the-box behaviour rejects { "a": 1, }.
  • Invalid input throws here. jsonc-parser is error-tolerant and returns a best-effort value alongside a list of errors, so { key: 1, "b": 2 } yields { "b": 2 } and three errors. Code that does not inspect that list silently loses the malformed member.
  • jsonc-parser is the larger tool, with a scanner, a syntax tree, and edit and format operations. rjson is a dependency-free parser plus the source-to-source toJson, including its offset-preserving mode.

JSON5 is a much wider dialect, and rjson is intentionally not it. On top of the comments and trailing commas allowed here, JSON5 also accepts unquoted object keys, single-quoted strings, strings continued across lines with a trailing backslash, hex numbers, numbers with a leading or trailing decimal point, numbers with an explicit plus sign, and NaN and Infinity. All of those are rejected here.

Not everything under Still rejected is a point of difference with JSON5, though: undefined, JavaScript expressions, nested block comments of the same style, and leading or doubled commas are rejected by JSON5 as well.

API

parse(source, reviver?) -> unknown
toJson(source, options?) -> string
stripComments(source, options?) -> string
stripTrailingCommas(source, options?) -> string

RELAXED_JSON_VERSION -> string   // dialect tag, currently 'relaxed-json.v0'

options is { preserveOffsets?: boolean }, defaulting to false.

stripTrailingCommas is deliberately conservative when used on its own: it will not look past a comment to find a closing brace, because it has no idea the comment is there. Use toJson to get both relaxations applied in the right order.

Structure

  • src/rjson.js - dependency-free parser and source transformer.
  • src/rjson.d.ts - hand-written TypeScript declarations.
  • demo/ - tiny browser demo bundled with esbuild through ESP.
  • scripts/ - build, smoke test, and pack-test scripts.

Changes are tracked in CHANGELOG.md, and the publish steps are in RELEASING.md.

License

MIT - see LICENSE.md.