Skip to content

Commit

Permalink
fs: introduce readJSON functions
Browse files Browse the repository at this point in the history
This adds `fs.readJSON`, `fs.readJSONSync`, `fsPromises.readJSON`, and
`fileHandle.readJSON`.
All of them take the usual parameters of their respective `readFile`
equivalents, with the addition of `JSON.parse`'s `reviver` option.
The file will be read as a string, with the encoding defaulting to
`'utf8'`, and if the read succeeds, `JSON.parse` will be called on the
result. Parsing errors are propagated the same way as reading errors.
  • Loading branch information
targos committed Mar 31, 2021
1 parent 3dee233 commit 91f3054
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 5 deletions.
118 changes: 118 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,33 @@ If one or more `filehandle.read()` calls are made on a file handle and then a
position till the end of the file. It doesn't always read from the beginning
of the file.
#### `filehandle.readJSON(options)`
<!-- YAML
added: REPLACEME
-->
* `options` {Object|string}
* `encoding` {string|null} **Default:** `'utf8'`
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
* `signal` {AbortSignal} allows aborting an in-progress readJSON
* Returns: {Promise} Fulfills upon a successful read with the contents of the
file parsed with [`JSON.parse`][].
Asynchronously reads the contents of a file and call [`JSON.parse`][] on it.
The `encoding` and `signal` options are passed to the underlying
[`fsPromises.readFile()`][] call and the `reviver` option is passed to
[`JSON.parse`][].
If `options` is a string, then it specifies the `encoding`.
The {FileHandle} has to support reading.
If one or more `filehandle.read()` calls are made on a file handle and then a
`filehandle.readJSON()` call is made, the data will be read from the current
position till the end of the file. It doesn't always read from the beginning
of the file.
#### `filehandle.readv(buffers[, position])`
<!-- YAML
added:
Expand Down Expand Up @@ -987,6 +1014,39 @@ system requests but rather the internal buffering `fs.readFile` performs.
Any specified {FileHandle} has to support reading.
### `fsPromises.readJSON(path[, options])`
<!-- YAML
added: REPLACEME
-->
* `path` {string|Buffer|URL|FileHandle} filename or `FileHandle`
* `options` {Object|string}
* `encoding` {string|null} **Default:** `'utf8'`
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
* `signal` {AbortSignal} allows aborting an in-progress readFile
* Returns: {Promise} Fulfills with the contents of the file parsed with
[`JSON.parse`][].
Asynchronously reads the contents of a file and call [`JSON.parse`][] on it.
The `path` parameter, and the `encoding`, `flag` and `signal` options are passed
to the underlying [`fsPromises.readFile()`][] call and the `reviver` option is
passed to [`JSON.parse`][].
If `options` is a string, then it specifies the encoding.
It is possible to abort an ongoing `readJSON` using an {AbortSignal}. If a
request is aborted the promise returned is rejected with an `AbortError`.
Any specified {FileHandle} has to support reading.
```mjs
import { readJSON } from 'fs/promises';
const json = await readJSON('package.json');
```
### `fsPromises.readlink(path[, options])`
<!-- YAML
added: v10.0.0
Expand Down Expand Up @@ -2952,6 +3012,35 @@ The Node.js GitHub issue [#25741][] provides more information and a detailed
analysis on the performance of `fs.readFile()` for multiple file sizes in
different Node.js versions.

### `fs.readJSON(path[, options], callback)`
<!-- YAML
added: REPLACEME
-->

* `path` {string|Buffer|URL|integer} filename or file descriptor
* `options` {Object|string}
* `encoding` {string|null} **Default:** `'utf8'`
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
* `signal` {AbortSignal} allows aborting an in-progress readJSON
* `callback` {Function}
* `err` {Error|AggregateError}
* `data` {any}

Asynchronously reads the contents of a file and call [`JSON.parse`][] on it.

The `path` parameter, and the `encoding`, `flag` and `signal` options are passed
to the underlying [`fs.readFileSync()`][] call and the `reviver` option is
passed to to [`JSON.parse`][].

For detailed information, see the documentation of [`fs.readFile()`][].

```mjs
import { readJSON } from 'fs';

readJSON('package.json', callback);
```
### `fs.readlink(path[, options], callback)`
<!-- YAML
added: v0.1.31
Expand Down Expand Up @@ -4618,6 +4707,33 @@ readFileSync('<directory>');
readFileSync('<directory>'); // => <data>
```
### `fs.readJSONSync(path[, options])`
<!-- YAML
added: REPLACEME
-->
* `path` {string|Buffer|URL|integer} filename or file descriptor
* `options` {Object|string}
* `encoding` {string|null} **Default:** `'utf8'`
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
* Returns: {any}
Reads the contents of the `path` and returns the result of calling
[`JSON.parse`][] on it.
The `path` parameter, and the `encoding` and `flag` options are passed to the
underlying [`fs.readFileSync()`][] call and the `reviver` option is passed to
to [`JSON.parse`][].
For detailed information, see the documentation of [`fs.readFile()`][].
```mjs
import { readJSONSync } from 'fs';

const json = readJSONSync('package.json');
```
### `fs.readlinkSync(path[, options])`
<!-- YAML
added: v0.1.31
Expand Down Expand Up @@ -6659,6 +6775,7 @@ the file contents.
[`AHAFS`]: https://www.ibm.com/developerworks/aix/library/au-aix_event_infrastructure/
[`Buffer.byteLength`]: buffer.md#buffer_static_method_buffer_bytelength_string_encoding
[`FSEvents`]: https://developer.apple.com/documentation/coreservices/file_system_events
[`JSON.Parse`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
[`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
[`ReadDirectoryChangesW`]: https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-readdirectorychangesw
[`UV_THREADPOOL_SIZE`]: cli.md#cli_uv_threadpool_size_size
Expand Down Expand Up @@ -6701,6 +6818,7 @@ the file contents.
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
[`fsPromises.open()`]: #fs_fspromises_open_path_flags_mode
[`fsPromises.opendir()`]: #fs_fspromises_opendir_path_options
[`fsPromises.readFile()`]: #fs_fspromises_readfile_path_options
[`fsPromises.rm()`]: #fs_fspromises_rm_path_options
[`fsPromises.utimes()`]: #fs_fspromises_utimes_path_atime_mtime
[`inotify(7)`]: https://man7.org/linux/man-pages/man7/inotify.7.html
Expand Down
35 changes: 35 additions & 0 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const kIoMaxLength = 2 ** 31 - 1;
const {
ArrayPrototypePush,
BigIntPrototypeToString,
JSONParse,
MathMax,
Number,
ObjectCreate,
Expand Down Expand Up @@ -360,6 +361,27 @@ function readFile(path, options, callback) {
req);
}

function readJSON(path, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { encoding: 'utf8', flag: 'r' });

const { reviver } = options;
if (typeof reviver !== 'undefined') {
validateFunction(reviver, 'options.reviver');
}

readFile(path, options, (readFileError, file) => {
if (readFileError) return callback(readFileError);
let json;
try {
json = JSONParse(file, reviver);
} catch (parseError) {
return callback(parseError);
}
callback(null, json);
});
}

function tryStatSync(fd, isUserFd) {
const ctx = {};
const stats = binding.fstat(fd, false, undefined, ctx);
Expand Down Expand Up @@ -448,6 +470,17 @@ function readFileSync(path, options) {
return buffer;
}

function readJSONSync(path, options) {
options = getOptions(options, { encoding: 'utf8', flag: 'r' });

const { reviver } = options;
if (typeof reviver !== 'undefined') {
validateFunction(reviver, 'options.reviver');
}

return JSONParse(readFileSync(path, options), reviver);
}

function defaultCloseCallback(err) {
if (err != null) throw err;
}
Expand Down Expand Up @@ -2157,6 +2190,8 @@ module.exports = fs = {
readvSync,
readFile,
readFileSync,
readJSON,
readJSONSync,
readlink,
readlinkSync,
realpath,
Expand Down
19 changes: 19 additions & 0 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const kWriteFileMaxChunkSize = 512 * 1024;
const {
ArrayPrototypePush,
Error,
JSONParse,
MathMax,
MathMin,
NumberIsSafeInteger,
Expand Down Expand Up @@ -71,6 +72,7 @@ const {
validateAbortSignal,
validateBoolean,
validateBuffer,
validateFunction,
validateInteger,
validateUint32
} = require('internal/validators');
Expand Down Expand Up @@ -146,6 +148,10 @@ class FileHandle extends EventEmitterMixin(JSTransferable) {
return fsCall(readFile, this, options);
}

readJSON(options) {
return fsCall(readJSON, this, options);
}

stat(options) {
return fsCall(fstat, this, options);
}
Expand Down Expand Up @@ -718,6 +724,18 @@ async function readFile(path, options) {
return PromisePrototypeFinally(readFileHandle(fd, options), fd.close);
}

async function readJSON(path, options) {
options = getOptions(options, { encoding: 'utf8', flag: 'r' });

const { reviver } = options;
if (typeof reviver !== 'undefined') {
validateFunction(reviver, 'options.reviver');
}

const file = await readFile(path, options);
return JSONParse(file, reviver);
}

module.exports = {
exports: {
access,
Expand Down Expand Up @@ -747,6 +765,7 @@ module.exports = {
writeFile,
appendFile,
readFile,
readJSON,
watch,
},

Expand Down
3 changes: 1 addition & 2 deletions lib/internal/source_map/source_map_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ function lineLengths(content) {

function sourceMapFromFile(mapURL) {
try {
const content = fs.readFileSync(fileURLToPath(mapURL), 'utf8');
const data = JSONParse(content);
const data = fs.readJSONSync(fileURLToPath(mapURL));
return sourcesToAbsolute(mapURL, data);
} catch (err) {
debug(err.stack);
Expand Down
2 changes: 1 addition & 1 deletion test/common/wpt.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ class StatusLoader {
load() {
const dir = path.join(__dirname, '..', 'wpt');
const statusFile = path.join(dir, 'status', `${this.path}.json`);
const result = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
const result = fs.readJSONSync(statusFile);
this.rules.addRules(result);

const subDir = fixtures.path('wpt', this.path);
Expand Down
3 changes: 1 addition & 2 deletions test/internet/test-trace-events-dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ for (const tr in tests) {

const file = path.join(tmpdir.path, traceFile);

const data = fs.readFileSync(file);
const traces = JSON.parse(data.toString()).traceEvents
const traces = fs.readJSONSync(file).traceEvents
.filter((trace) => trace.cat !== '__metadata');

assert(traces.length > 0);
Expand Down
Loading

0 comments on commit 91f3054

Please sign in to comment.