Library / SDK
jprichardson/node-jsonfile avatar
jprichardson/node-jsonfile

jsonfile: the four functions around JSON.parse and fs.writeFile

Easily read/write JSON files.

1,212 stars387 forksJavaScriptMIT

At a glance

What is it?
A small Node.js module whose entire reason to exist is one paragraph of the README: writing JSON.stringify() and then fs.writeFile(), and JSON.parse() with fs.readFile() inside try and catch, became annoying. The API is four functions, and the options object is where the real behaviour lives.
Who is it for?
jsonfile fits a Node.js codebase that reads and writes JSON configuration or data files and wants one import instead of a read, a parse and a try and catch at every call site, and it costs one runtime dependency and one optional one. It is the wrong tool for a data file that has to be validated, for a file that is large enough to stream, and for a write that has to be atomic, since nothing here stops a process dying halfway through a write and leaving a truncated file.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The reason this module exists

The README explains the purpose in one paragraph, and it is the best summary of the design.

Writing `JSON.stringify()` and then `fs.writeFile()` and `JSON.parse()` with `fs.readFile()` enclosed in `try/catch` blocks became annoying.

That sentence contains the whole argument for a library that most Node developers would not bother to install. The pattern it describes is four or five lines at every call site, and the annoying part is not the length but the fact that the error handling has to be repeated at every one of them, with the parse error and the read error ending up in the same catch block and therefore being indistinguishable to the caller.

The module is called jsonfile on npm, its description is Easily read/write JSON files, and the README says at the top that it cannot be used in the browser, which is the direct consequence of it sitting on top of the `fs` module.

The package is MIT licensed, with the copyright line naming JP Richardson and the years 2012 to 2016. The npm version in the repository is 6.2.1, the default branch is `master`, and the last push to the repository was on 2026-04-20. There are no GitHub releases, so the changelog in the repository is the version history.

The repository is correspondingly small. There are two source files, `index.js` and `utils.js`, a `test/` directory, the `package.json`, an `appveyor.yml` for the Windows build, and an `.editorconfig`. The `files` array in the package metadata lists exactly the two JavaScript files that ship, which is what keeps the install small.

Four functions, and an options object that does the work

The API is four functions, listed at the top of the README.

`readFile(filename, [options], callback)` and `readFileSync(filename, [options])` read and parse. `writeFile(filename, obj, [options], callback)` and `writeFileSync(filename, obj, [options])` serialise and write. So for both the read side and the write side there is an asynchronous version with a callback and a synchronous version without one.

The options object is where the module earns its place, and it has two halves. The first is pass-through: for reading, you may pass any `fs.readFile` options or set `reviver` for a JSON reviver, and for writing, any `fs.writeFile` options or set `replacer` for a JSON replacer. The second is the module's own additions, and there are only three of them: `throws`, `spaces` and the line-ending options.

The pass-through half is a real design decision rather than a convenience. Because the options object is forwarded to `fs.readFile` and `fs.writeFile`, the encoding, the flag, the mode and everything else the filesystem offers are available without the module having to know about them, and they will keep working as the filesystem gains more.

The examples in the README are the shortest possible illustration of the shape of a call:

js
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
const obj = { name: 'JP' }

jsonfile.writeFile(file, obj, function (err) {
  if (err) console.error(err)
})

The asynchronous read is the same shape, and the synchronous variants are the same call without the callback.

The throws flag is the decision that matters

One option carries the weight, and it is the one a caller has to think about.

For `readFile`, `throws` is a boolean that defaults to `true`. If `JSON.parse` throws an error, the error is passed to the callback. If `throws` is `false`, the method returns `null` for the object instead. The synchronous version behaves the same way with respect to the error: if an error is encountered reading or parsing the file, throw the error, and if `throws` is `false`, return `null` for the object.

So the same call can be an error path or a value path depending on a flag, and the two are genuinely different. A configuration file that is missing or malformed is an error the caller must handle in one mode, and an ordinary null in the other. A caller who has to distinguish a missing file from a file containing null will find the `false` mode loses that distinction, because the read error and the parse error both collapse into the same null.

The other half of the read API is the promise form. The `readFile` method will return a promise if you do not pass a callback function, so the same function serves both styles:

js
const jsonfile = require('jsonfile')
const file = '/tmp/data.json'
jsonfile.readFile(file)
  .then(obj => console.dir(obj))
  .catch(error => console.error(error))

That dual signature is a small thing that removes a decision from every call site, and it is the sort of detail that is either annoying or invisible depending on whether your project mixes callbacks and promises.

Writing: spaces, line endings and appending

The write side has three options of its own, and they are all about the bytes on disk rather than about the data.

`spaces` is the familiar `JSON.stringify` indentation option, and it is the one most people want:

js
jsonfile.writeFile(file, obj, { spaces: 2 }, function (err) {
  if (err) console.error(err)
})

The line-ending options are the unusual ones. You can override the `EOL` string, and you can set a `finalEOL` flag to `false` to not save the file with `EOL` at the end:

js
jsonfile.writeFile(file, obj, { spaces: 2, EOL: '\r\n' }, function (err) {
  if (err) console.error(err)
})
js
jsonfile.writeFile(file, obj, { spaces: 2, finalEOL: false }, function (err) {
  if (err) console.log(err)
})

The reason to want these is a repository. A JSON file that is committed to version control and rewritten by a tool will produce a diff on every line if the line endings differ from what the repository expects, and a trailing newline that the tool adds or removes produces a diff on every write as well. The `EOL` option covers the first case, and `finalEOL` covers the second.

Appending is not a module feature but a pass-through, and the README says so directly. You can use the `fs.writeFile` option `{ flag: 'a' }` to achieve it:

js
jsonfile.writeFile(file, obj, { flag: 'a' }, function (err) {
  if (err) console.error(err)
})

The consequence is worth being explicit about, because the option reads like a JSON operation and is not one. The append is a byte-level append performed by `fs.writeFile`, so what lands in the file is the serialised object concatenated onto whatever was already there, not a JSON array with the new element added. A file that already contains a JSON document will not contain valid JSON afterwards, and the module does not claim otherwise.

One runtime dependency, one optional, and a two-file package

The dependency list in `package.json` is worth a look, because for a module this small it explains most of the design.

There is exactly one runtime dependency, `universalify` at `^2.0.0`, and that is the package that turns a callback-style function into one that also returns a promise. It is the reason the dual callback and promise API costs nothing in terms of the module's own code, and it is a good example of a dependency that earns its place.

There is one optional dependency, `graceful-fs` at `^4.1.6`, and optional is the operative word: it is not required, but it is used when it is present, and the point of a graceful file system module is to survive the situations where a normal one does not.

The development dependencies are equally small: `mocha` at `^8.2.0` for the tests, `rimraf` at `^2.4.0`, and `standard` at `^16.0.1` for the linting. The `standard` dependency is the reason for the Standard JavaScript badge in the README, and it is also the shape of the scripts, where the lint and the unit tests are chained:

json
  "scripts": {
    "lint": "standard",
    "test": "npm run lint && npm run unit",
    "unit": "mocha"
  }

The `test` script runs the linter before the unit tests, so a style violation fails the test run rather than being a separate concern. Continuous integration runs on two systems, a Linux workflow on GitHub Actions and a Windows build on AppVeyor, which is the right pair for a module whose whole job is filesystem behaviour, since path and newline handling differ between them and the `EOL` option only makes sense on one of them.

The code style visible in the README examples follows the same convention: no semicolons, single quotes, and `require` rather than `import`.

Where jsonfile is the wrong tool

The limits are those of the pattern it wraps, and a reviewer should know them before adopting it.

The first is that nothing here is atomic. The module calls `fs.writeFile`, and a write that is interrupted by a crash or a full disk leaves a truncated file, exactly as it would without the module. A configuration file that is rewritten on every run of a tool is a file that can be corrupted by a crash, and jsonfile does not address that, because addressing it means writing to a temporary file and renaming, which is a different design decision for a different library.

The second is that the `throws` option is about parsing, not about validity. `JSON.parse` accepts any JSON document, so a file containing an object with the wrong shape is read successfully and handed to the caller as if it were right. A module that offered validation would need a schema, and the moment it does it stops being a four-function wrapper.

The third is size and streaming. The synchronous variants read the whole file into memory and block the event loop while they do it, and the asynchronous variant also reads the whole file. For a configuration file that is exactly right, and for a multi-gigabyte export that is a problem the module does not address.

The fourth is concurrency. Two processes writing the same JSON file will not coordinate, and the last writer wins, or the file is a mixture, and there is no locking in the module.

The alternative, for a project that only needs a couple of files, is to write the four lines yourself, which the README argues is the thing that became annoying, and it remains a valid position for a codebase with a house style for filesystem access. The difference in approach is that jsonfile centralises the options, gives every call site the same error semantics, and offers the promise variant for free, while doing it yourself gives you control over the atomicity, the validation and the locking that the wrapper does not provide. For most projects the wrapper is the cheaper of the two, and for the projects that need those three things it is the wrong dependency.

Editorial conclusion

jsonfile fits a Node.js codebase that reads and writes JSON configuration or data files and wants one import instead of a read, a parse and a try and catch at every call site, and it costs one runtime dependency and one optional one. It is the wrong tool for a data file that has to be validated, for a file that is large enough to stream, and for a write that has to be atomic, since nothing here stops a process dying halfway through a write and leaving a truncated file. Before adopting it, read the `throws` option carefully, because the default of true and the alternative of null are very different failure modes for a caller, and note that the module states plainly that it cannot be used in the browser, so it belongs in the server half of a project and not in a bundled front end.

Frequently asked questions

How do I install jsonfile?

With `npm install --save jsonfile`. The package is MIT licensed, its npm version in the repository is 6.2.1, and it ships two files, index.js and utils.js, with one runtime dependency on universalify and an optional dependency on graceful-fs.

How do I read a JSON file with jsonfile?

Call `jsonfile.readFile(file, callback)` for the callback form, or call `jsonfile.readFile(file)` with no callback to get a promise, and use `jsonfile.readFileSync(file)` for the synchronous variant. The options object accepts any fs.readFile options and a `reviver` for JSON parsing.

What does the throws option do in jsonfile?

It defaults to true, so a failure to parse the JSON is passed to the callback, and the synchronous variant throws. Set it to false and the object is null instead, which is a different failure mode for the caller and loses the distinction between a read error and a parse error.

Can I use jsonfile in the browser?

No. The README states at the top that this module cannot be used in the browser, because it reads and writes files through the Node.js fs module.

Official sources

  1. Issues
  2. jprichardson/node-jsonfile on GitHub
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jprichardson-node-jsonfile.svg)](https://hysenlabs.com/projects/jprichardson-node-jsonfile)

Was this useful?