@stoplight/json
@stoplight/json is a TypeScript utility collection for source-aware JSON work, especially API-description tooling. Its main parser returns decoded data, diagnostics, an abstract syntax tree, and a line-offset map, which lets editors translate between cursor positions and JSON paths. The package also converts JSON paths to URI-fragment pointers, detects and resolves reference shapes, traverses objects, preserves key order, and offers non-throwing parse and cycle-tolerant stringify helpers. It is broader than a JSON parser and carries conventions from Stoplight's OpenAPI toolchain.
Useful infrastructure for editors and API tooling that genuinely need diagnostics, AST ranges, position mapping, and Stoplight reference helpers together. For ordinary JSON parsing, pointer handling, or cycle-safe output, pick the focused underlying package and avoid the surprising safeParse and safeStringify semantics.
Use it if
- You need parsed JSON data plus diagnostics and AST ranges for an editor, linter, or API-description tool
- You need to map zero-based line and character positions to JSON paths and back
- Your code already uses Stoplight types, JSON references, or ordered-object utilities and benefits from matching helpers
- You need URI-fragment JSON Pointer encoding and decoding for paths containing slashes, tildes, or Unicode
- You only need JSON.parse or JSON.stringify: this package installs lodash, jsonc-parser, safe-stable-stringify, and three Stoplight packages for a much broader API
- You expect safeParse to behave like a typical result wrapper: invalid input returns undefined, non-string input is returned unchanged, and a precision-losing numeric string can be returned as a string
- You expect safeStringify to mirror JSON.stringify for strings: the implementation returns a string input unchanged instead of adding JSON quotes
- You need current jsonc-parser behavior or minimal dependency drift: version 3.21.7 pins jsonc-parser to the 2.2 line and targets Node 8.3-era compatibility
- You want documentation that exactly matches the current return shape: the README describes result.pointers, while current parseWithPointers source returns data, diagnostics, ast, and lineMap
Setup reality
Install with npm install @stoplight/json. There are no peers, native builds, credentials, or required config, and the package publishes CommonJS, an ESM module field, and TypeScript declarations. The runtime dependency tree is not tiny: lodash, jsonc-parser 2.2.x, safe-stable-stringify, @stoplight/path, @stoplight/types, and @stoplight/ordered-object-literal are direct dependencies. API naming hides several important contracts. parseWithPointers does not throw for ordinary syntax errors; inspect result.diagnostics before trusting result.data, and use result.ast plus result.lineMap for location helpers. Its default options disallow comments, but passing your own options object replaces that default, so state disallowComments explicitly when combining it with duplicate-key or key-order settings. Line and character positions are zero-based. pathToPointer returns URI-fragment form beginning with #, and pointerToPath rejects a pointer without that hash; use another helper if you need bare /a/b pointer syntax. safeParse returns undefined on invalid JSON, but it returns non-string inputs unchanged and preserves a numeric string when conversion would change its textual representation. safeStringify first tries JSON.stringify, then falls back to safe-stable-stringify for cycles, yet it returns a top-level string unchanged. stringify wraps that helper and throws only when no string can be produced. AST nodes have parent links, so they are cyclic even though the root parent is removed. preserveKeyOrder creates special ordered objects and can affect enumeration through Proxy-related helpers. Browser use is advertised, but the collection includes much more than most front-end bundles need. Import focused functions and confirm tree-shaking in your actual bundler.
Patterns
Parse data and check syntax diagnosticsparse-with-diagnostics
import { parseWithPointers } from '@stoplight/json';
const result = parseWithPointers('{"name": "Ada",}');
if (result.diagnostics.length > 0) {
console.error(result.diagnostics);
} else {
console.log(result.data);
}Syntax problems are reported in diagnostics rather than thrown. Do not treat data as valid before checking that array.
Parse JSON with comments intentionallyallow-json-comments
const result = parseWithPointers(
'{ /* owner */ "name": "Ada" }',
{ disallowComments: false }
);Comments are disallowed by the default options object. Passing a custom options object changes that contract, so keep the choice explicit.
Report duplicate object keysdetect-duplicate-keys
const result = parseWithPointers(
'{"port": 3000, "port": 4000}',
{ disallowComments: true, ignoreDuplicateKeys: false }
);
const duplicates = result.diagnostics.filter((d) => d.message === 'DuplicateKey');Duplicate detection is enabled only when ignoreDuplicateKeys is exactly false. The decoded object still cannot retain both values under one key.
Find the JSON path at a cursor positionmap-position-to-path
import { getJsonPathForPosition, parseWithPointers } from '@stoplight/json';
const parsed = parseWithPointers(`{
"address": { "street": 123 }
}`);
const path = getJsonPathForPosition(parsed, { line: 1, character: 25 });
// ['address', 'street'] for a position inside that valueLine and character indexes are zero-based. Out-of-range positions and positions without a matching path can return undefined.
Find the source range for a JSON pathmap-path-to-location
import { getLocationForJsonPath } from '@stoplight/json';
const location = getLocationForJsonPath(parsed, ['address', 'street']);
if (location) console.log(location.range.start, location.range.end);The helper needs the parseWithPointers result because it walks that result's AST. It returns undefined when the exact path is missing.
Convert a path to URI-fragment JSON Pointerencode-json-pointer
import { pathToPointer } from '@stoplight/json';
const pointer = pathToPointer(['paths', '/users', 'get']);
// '#/paths/~1users/get'The return value begins with # and percent-encodes URI-fragment content. Slash and tilde inside segments receive JSON Pointer escaping.
Convert a URI-fragment pointer to a pathdecode-json-pointer
import { pointerToPath } from '@stoplight/json';
const path = pointerToPath('#/paths/~1users/get');
// ['paths', '/users', 'get']pointerToPath requires the # prefix. A bare '/paths/~1users/get' string throws URIError.
Parse without a try and catch blocksafe-parse-invalid-json
import { safeParse } from '@stoplight/json';
const value = safeParse('{broken');
if (value === undefined) {
console.log('invalid JSON');
}undefined is also a possible outcome for JSON.stringify-related values elsewhere, so do not use this helper when you need structured error details.
Notice precision-sensitive numeric inputpreserve-large-number-text
const value = safeParse('9007199254740993');
console.log(typeof value, value);
// string, '9007199254740993'safeParse returns the original string when JavaScript number conversion would change its textual value. This differs from JSON.parse, which returns a number.
Serialize a circular objectstringify-circular-data
import { safeStringify } from '@stoplight/json';
const user = { name: 'Ada' };
user.self = user;
const json = safeStringify(user, null, 2);The helper tries JSON.stringify first, then safe-stable-stringify. A top-level string is returned unchanged rather than JSON-quoted.
Replace cycles with pointer referencesdecycle-with-references
import { decycle } from '@stoplight/json';
const root = { name: 'root' };
root.child = { parent: root };
console.log(decycle(root));
// { name: 'root', child: { parent: { $ref: '#' } } }decycle creates a copied structure and represents an ancestor cycle as a {$ref} object using URI-fragment JSON Pointer syntax.
Visit every object property with its parent pathtraverse-properties
import { traverse } from '@stoplight/json';
traverse(document, ({ parentPath, property, propertyValue }) => {
if (property === '$ref') {
console.log([...parentPath, property], propertyValue);
}
});The traversal recursively follows every non-null object and has no cycle detection. Decycle or guard cyclic inputs before using it.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| jsonc-parser | npm | You mainly need tolerant parsing, AST edits, locations, and JSON-with-comments support without Stoplight's larger helper collection |
| json-source-map | npm | You need decoded JSON plus direct source locations for values and keys through JSON Pointer paths |
| json-pointer | npm | You only need RFC 6901 pointer compile, get, set, and remove operations |
| safe-stable-stringify | npm | Your only requirement is deterministic cycle-safe stringification with a focused API |