format-json-files
v2.0.0
Published
Format JSON files in place: one file, or every .json file in a directory tree. Never rewrites a file it cannot keep exactly. Key sorting, check mode for CI, a CLI. Zero dependencies, TypeScript, ESM and CommonJS, Node 20+.
Maintainers
Readme
format-json-files
Format JSON files in place: one file, or every .json file in a directory and its subdirectories. It never rewrites a file it cannot keep exactly: a file with a 20-digit id, a repeated key or bytes that are not UTF-8 is left alone and reported. It can also sort keys, and check a tree in CI without writing anything. There is a command-line tool too.
- TypeScript types, ES module and CommonJS builds, no dependencies.
- Node 20 and later. Bun and Deno run it through their Node compatibility.
Install
npm install format-json-filesUsage
import formatJsonFiles from 'format-json-files';
const report = formatJsonFiles('./data');
// {changed: ['data/a.json'], unchanged: ['data/b.json'], skipped: [{path: 'data/c.json', reason: 'duplicate key: "id"'}]}CommonJS, as in 1.x:
const formatJsonFiles = require('format-json-files');
formatJsonFiles('./data', {indent: 2, sortKeys: true});Command line:
npx format-json-files ./data
npx format-json-files --sort-keys --indent 2 config.json settings.json
npx format-json-files --check . # in CI: lists the files that would change, exits 1 if anyAPI
formatJsonFiles(path, options?)
Formats the file at path whatever its name, or, when path is a directory, every file whose name ends in .json (in any case) in it and its subdirectories. It works synchronously and returns a report:
| Field | What it holds |
|---|---|
| changed | Files rewritten; with check, the files that would be rewritten |
| unchanged | Files already formatted; they are not written, so their modification times stay |
| skipped | {path, reason} for each file left alone |
Options (the defaults give 1.x's output):
| Option | Default | What it does |
|---|---|---|
| indent | 4 | Spaces per level, 0 to 10, or '\t' for tabs |
| sortKeys | false | Sort object keys at every level, by UTF-16 code unit; arrays keep their order |
| check | false | Write nothing; report what would change |
| finalNewline | false | End each file with a line break |
| eol | 'lf' | 'lf', 'crlf', or 'auto' to keep each file's (CRLF when the file has any) |
| ignore | ['node_modules', '.git'] | Directory names the walk does not enter; a list replaces the default, [] walks everything |
It throws an Error('Path argument not set') when path is empty or missing, a TypeError when path is not a string or an option is invalid, and an Error('Invalid path') when path is neither a file nor a directory. A problem with a single file never throws: the file goes in skipped. A second argument that is not an object is ignored, as 1.x ignored it.
The function is also exported by name (import {formatJsonFiles} from 'format-json-files'), and the types FormatOptions, FormatReport and SkippedFile are exported for both module systems.
Command line
format-json-files [options] <path> [<path> ...]
--indent <n|tab> Spaces per level, 0 to 10, or "tab" (default 4)
--sort-keys Sort object keys, at every level
--check Write nothing; list the files that would change
--final-newline End each file with a line break
--eol <lf|crlf|auto> Line breaks to write (default lf)
--ignore <name> A directory name not to enter; repeatable; replaces the default list
--no-ignore Enter every directory, node_modules and .git too
-h, --help Show the help
-v, --version Show the versionIt prints nothing when every file was formatted. Each skipped file goes to stderr as path: reason. It exits 1 when a file was skipped, when --check found a file to change, or on a usage error.
Behaviour at the edges
| Input | What happens |
|---|---|
| A number written as an integer that JSON.stringify would write with other digits (12345678901234567890, 1000000000000000000000) | Skipped: number cannot be kept exactly |
| A number written with a fraction or exponent (0.1000000000000000055, 6.02e23) | Rounded to the nearest double as JSON.parse does, and formatted (0.1, 6.02e+23) |
| -0, 1e400, 1e-400 | Skipped (they would become 0, null and 0) |
| A key repeated in one object | Skipped: duplicate key |
| Bytes that are not UTF-8, or a UTF-16 file | Skipped: not UTF-8 |
| A UTF-8 byte order mark | Dropped; the file is formatted |
| Invalid JSON, JSON with comments, an empty file | Skipped: not valid JSON: ... |
| Nesting deeper than the JavaScript stack allows (about 10,000 levels) | Skipped: nested too deeply |
| Keys that look like numbers ("10", "2") | Written first, in numeric order, as JSON.stringify does; sortKeys gives a plain sorted order |
| A symbolic link named *.json inside the directory | Skipped: symbolic link (the walk never writes through a symbolic link) |
| A hard link | Rewritten in place like any file, so every name for it sees the change |
| A link to a directory inside the directory | Not entered |
| A file that cannot be written (read-only) | Skipped: cannot write: EPERM or EACCES |
Migrating from 1.x
formatJsonFiles(path)still works and writes the same bytes for every file 1.x formatted without loss. Files 1.x would have damaged are now left alone; checkreport.skipped(or the command line's exit code) to see them.- 1.x printed problems with
console.logand returned nothing. Read the returned report instead. node_modulesand.gitare no longer walked. Pass{ignore: []}or--no-ignoreto keep 1.x's walk.- The command-line tool exits 1 when a file was skipped. A script that ran it on files it could not parse, and relied on exit code 0, needs to fix or exclude those files.
- Node 20 or later.
Limits and what it is not
- It rewrites files in place. Run it on files you have committed or backed up.
- It is synchronous, and reads each whole file into memory.
- It is not a validator or a linter, and it does not read JSON5 or JSON with comments.
- It does not keep the text of numbers.
1.0is written as1,1e5as100000, and"é"as"é", asJSON.stringifywrites them.
License
MIT, see LICENSE.
