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

@helpers-work/json-sort

v0.1.1

Published

Sort JSON object keys deterministically and format the result. Library and CLI for files and CI.

Readme

json-sort

npm CI License: MIT

Sort the keys of JSON objects in a deterministic order and format the result. Use it as a library in Node.js or the browser, or as a CLI to normalize JSON files and check them in CI.

Try it online: JSON Sort on Helpers.work is built on this package and runs entirely in your browser.

Typical uses: keep configuration files and test fixtures in one canonical key order, and reduce noise in diffs.

  • Sorts object keys; never reorders array elements.
  • formatJson copies string and number literals from the input verbatim — 9007199254740993, 1.2300 and 1e400 stay exactly as written.
  • Strict JSON only: comments, trailing commas and duplicate keys are errors.
  • Locale-independent ordering by UTF-16 code units, with optional case-insensitive and numeric ("natural") comparison and pinned keys.
  • One runtime dependency: jsonc-parser, used as the tokenizer/parser.

Installation

npm install @helpers-work/json-sort

The CLI can run without installing the package:

npx @helpers-work/json-sort data.json

Quick example

import { formatJson, stringifySorted } from "@helpers-work/json-sort";

formatJson('{"b":1,"a":{"d":2,"c":3}}');
// {
//   "a": {
//     "c": 3,
//     "d": 2
//   },
//   "b": 1
// }

stringifySorted(
  { name: "Alice", id: 42, email: "[email protected]" },
  { pinnedKeys: ["id"], indent: 0 },
);
// '{"id":42,"email":"[email protected]","name":"Alice"}'

CommonJS works too:

const { formatJson, stringifySorted } = require("@helpers-work/json-sort");

API

Both functions are synchronous, return a JSON string without a trailing newline, and never modify their arguments. They throw JsonSortError on failure.

formatJson(text, options?)

Parses strict JSON text (RFC 8259) and returns it with sorted keys and the requested indentation.

  • String literals (keys and values) are copied as written, including escapes such as "\u00e9". Keys are compared by their decoded value, so "\u0061" and "a" are the same key.
  • Number literals are copied as written. No value passes through a JavaScript Number, so large integers, trailing zeros, exponents and -0 are preserved.
  • A single leading byte order mark (U+FEFF) is removed. A BOM anywhere else is an error.
  • Allowed changes: property order, insignificant whitespace, line breaks, and removal of one leading BOM.
formatJson('{"z":1e+03,"id":9007199254740993,"a":1.2300}', { indent: 0 });
// '{"a":1.2300,"id":9007199254740993,"z":1e+03}'

stringifySorted(value, options?)

Serializes existing JavaScript data. Accepted values: strings, finite numbers, booleans, null, plain objects (including Object.create(null)), and dense arrays.

  • Only own, enumerable, string-keyed data properties are used. Non-enumerable and symbol-keyed properties are ignored.
  • Rejected with UNSUPPORTED_VALUE: undefined, functions, symbols, bigint, NaN, ±Infinity, Date, Map, Set, class instances, boxed primitives, array subclasses, sparse arrays, arrays with extra non-index properties, and accessor properties (getters are never invoked).
  • toJSON is never called.
  • Cycles are rejected with CIRCULAR_REFERENCE. The same object in two independent branches is fine.
  • Numbers are written from their JavaScript value (-0 stays -0). Precision lost when the value was created, for example by JSON.parse, cannot be restored. Use formatJson when you have the original text.
  • Keys like __proto__, constructor and prototype are treated as ordinary keys.

Options

| Option | Type | Default | Description | | --------------- | --------------------- | ------- | ----------------------------------------------------------------------------------------------------- | | order | 'asc' \| 'desc' | 'asc' | Direction for unpinned keys. | | recursive | boolean | true | Sort nested objects, including objects inside arrays. false sorts only the root object. | | caseSensitive | boolean | true | false compares toLowerCase() forms; ties are broken by the original strings. | | numeric | boolean | false | Compare runs of ASCII digits by numeric value: item2 before item10. | | pinnedKeys | readonly string[] | [] | Keys placed first, in this order, in every sorted object. Exact, case-sensitive match. No duplicates. | | indent | 0 \| 2 \| 4 \| '\t' | 2 | 0 produces compact output with no whitespace. | | maxDepth | integer 1–256 | 128 | Maximum nesting depth. The root is depth 0; each property value or array element adds 1. |

Unknown option names, wrong types and invalid values throw INVALID_OPTION. An option set to undefined uses its default.

Sort order

  • Default: lexicographic by UTF-16 code units, the same as comparing strings with < in JavaScript. It does not depend on the current locale. Example: "1", "10", "2", "A", "a", "b".
  • caseSensitive: false: a simple case-insensitive comparison using String.prototype.toLowerCase(). It is not linguistic collation for any particular language, and there is no Unicode normalization. "A" and "a" remain two different keys. When two keys compare equal case-insensitively, the original strings decide, so the result never depends on input order.
  • numeric: true: maximal runs of ASCII digits compare by value, with no length limit and no conversion to Number. Everything else compares by code unit. Signs, dots and exponents have no numeric meaning. item02 and item2 have the same value; the original strings break the tie, so item02 comes first in ascending order.
  • order: 'desc' reverses the order of unpinned keys only.
  • pinnedKeys come first in list order. Pinned keys that do not exist are ignored.

Errors

import { JsonSortError, formatJson } from "@helpers-work/json-sort";

try {
  formatJson('{"a":1,"a":2}');
} catch (error) {
  if (error instanceof JsonSortError) {
    error.code; // 'DUPLICATE_KEY'
    error.path; // ['a']
    error.offset; // 7  (0-based, UTF-16 code units)
    error.line; // 1  (1-based)
    error.column; // 8  (1-based)
  }
}

| code | Meaning | | -------------------- | --------------------------------------------------------------------------------- | | INVALID_JSON | Syntax error, comment, trailing comma, empty document, extra content, bad escape. | | DUPLICATE_KEY | The same decoded key appears twice in one object. | | UNSUPPORTED_VALUE | stringifySorted got a value that is not plain JSON data. | | CIRCULAR_REFERENCE | stringifySorted found a cycle. | | INVALID_OPTION | Unknown or invalid option. | | MAX_DEPTH_EXCEEDED | Nesting deeper than maxDepth. |

path is set for value errors. offset, line and column are set for errors in text input. Messages are in English and never include the document or values, though key names and paths may appear. Depth is checked with an iterative pre-scan before any recursive parsing, so very deep input fails with MAX_DEPTH_EXCEEDED rather than a stack overflow.

CLI

json-sort [file ...] [options]

--write              Rewrite files in place.
--check              Check sorting and formatting without changing files.
--desc               Sort unpinned keys in descending order.
--numeric            Compare digit runs numerically.
--ignore-case        Compare keys without case sensitivity.
--no-recursive       Sort only the root object.
--pin <key>          Pin a key first; repeat to set multiple keys.
--indent <0|2|4|tab>  Set indentation; default: 2.
--help               Show help.
--version            Show package version.
--                    End option parsing.

Install it as a dev dependency to use json-sort in npm scripts, or run it once with npx @helpers-work/json-sort.

json-sort data.json                      # print sorted JSON to stdout
cat data.json | json-sort                # read stdin (also: json-sort -)
json-sort data.json --indent 0           # compact output
json-sort data.json --pin id --pin name  # pinned keys
json-sort a.json b.json --check          # CI check
json-sort a.json b.json --write          # rewrite in place
  • The CLI uses formatJson and appends exactly one \n to its output.
  • Without --write or --check, it accepts one file or stdin. Multiple files in this mode are an error.
  • With no arguments, it reads stdin if stdin is a pipe. In an interactive terminal it prints usage and exits with 2.
  • --check compares each file byte-for-byte with the exact CLI output. It fails on unsorted keys and also on different indentation, CRLF line endings, a BOM, or a missing or extra final newline. File names that need changes are printed to stderr.
  • --write and --check cannot be combined. --write does not accept stdin. --check accepts one stdin input or files, not both.
  • Paths are taken literally. Directories and glob patterns are not expanded; let your shell expand globs.
  • Input must be valid UTF-8. Invalid byte sequences are an error, never silently replaced.
  • The total input per run is limited to 10 MiB.
  • In --check and --write modes stdout stays empty. Diagnostics go to stderr.

Exit codes

| Code | Meaning | | ---- | -------------------------------------------------------------------------- | | 0 | Success; with --check, all inputs are already formatted. | | 1 | --check found inputs that would change. | | 2 | Invalid JSON, invalid options or arguments, I/O error, or another failure. |

If any input fails, the exit code is 2 even when other inputs would return 1.

How --write changes files

  • All inputs are read, parsed and formatted before anything is written. If any input is invalid, no file is modified.
  • Each file is written to a temporary file in the same directory, flushed and closed, then renamed over the original. The original is never truncated first.
  • Permission bits are preserved on POSIX systems. File ownership and extended attributes are not carried over.
  • Files whose content would not change are left untouched.
  • Symbolic links are refused in --write mode.
  • Writing several files is not transactional. If an I/O error occurs while writing the third file, the first two have already been replaced.

Supported environments

  • Node.js 22 and 24, ESM (import) and CommonJS (require), with TypeScript declarations for both.
  • Browsers through a bundler. The library entry point does not use Node.js APIs, process, the DOM, the network or the file system. The CLI is a separate file and is not reachable from the library entry point.

Limitations

  • Arrays are not sorted. Only object keys are reordered.
  • The output is text, not an object. JavaScript orders integer-like property names such as "1" and "10" before other keys, whatever order they were inserted in. A JavaScript object therefore cannot hold an arbitrary key order, so both functions return JSON text.
  • Not canonical JSON. The package does not implement RFC 8785 (JCS). Semantically equal documents with different literals (1, 1.0, 1e0, or "é" and "\u00e9") stay different. Do not use the output as input for cryptographic signatures.
  • Literal preservation is not arbitrary-precision arithmetic. formatJson keeps number text unchanged; it never computes with numbers.
  • No JSON5, JSONC, or YAML, no comments and no trailing commas.
  • Proxies and cross-realm objects (for example from another iframe or vm context) are not supported by stringifySorted. Plain objects and arrays from another realm are rejected as non-plain.
  • Whole-document processing. Documents are processed in memory; the CLI limits the input size to 10 MiB per run.

Development

Requires Node.js 22.18+ or 24; tests run TypeScript sources through Node's built-in type stripping.

npm ci
npm run typecheck
npm run lint
npm test              # builds, then runs unit and CLI tests
npm run build
npm run test:package  # packs, installs the tarball into temporary consumer projects, runs a browser smoke test
npm pack --dry-run

npm run test:package runs a headless Chrome smoke test when Chrome is available. Set CHROME_BIN to use a specific binary, or REQUIRE_BROWSER=1 to fail when no browser is found.

To try an unreleased build in another project, install a local tarball:

npm run build
npm pack                      # creates helpers-work-json-sort-<version>.tgz
cd /path/to/your/project
npm install /path/to/helpers-work-json-sort-<version>.tgz

Support

About

Created for Helpers.work, a collection of practical developer tools. The online JSON Sort tool uses this package.

License

MIT © Julian Halden