Skip to content

Commit 91f3054

Browse files
committed
fs: introduce readJSON functions
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.
1 parent 3dee233 commit 91f3054

File tree

7 files changed

+291
-5
lines changed

7 files changed

+291
-5
lines changed

doc/api/fs.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,33 @@ If one or more `filehandle.read()` calls are made on a file handle and then a
332332
position till the end of the file. It doesn't always read from the beginning
333333
of the file.
334334
335+
#### `filehandle.readJSON(options)`
336+
<!-- YAML
337+
added: REPLACEME
338+
-->
339+
340+
* `options` {Object|string}
341+
* `encoding` {string|null} **Default:** `'utf8'`
342+
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
343+
* `signal` {AbortSignal} allows aborting an in-progress readJSON
344+
* Returns: {Promise} Fulfills upon a successful read with the contents of the
345+
file parsed with [`JSON.parse`][].
346+
347+
Asynchronously reads the contents of a file and call [`JSON.parse`][] on it.
348+
349+
The `encoding` and `signal` options are passed to the underlying
350+
[`fsPromises.readFile()`][] call and the `reviver` option is passed to
351+
[`JSON.parse`][].
352+
353+
If `options` is a string, then it specifies the `encoding`.
354+
355+
The {FileHandle} has to support reading.
356+
357+
If one or more `filehandle.read()` calls are made on a file handle and then a
358+
`filehandle.readJSON()` call is made, the data will be read from the current
359+
position till the end of the file. It doesn't always read from the beginning
360+
of the file.
361+
335362
#### `filehandle.readv(buffers[, position])`
336363
<!-- YAML
337364
added:
@@ -987,6 +1014,39 @@ system requests but rather the internal buffering `fs.readFile` performs.
9871014
9881015
Any specified {FileHandle} has to support reading.
9891016
1017+
### `fsPromises.readJSON(path[, options])`
1018+
<!-- YAML
1019+
added: REPLACEME
1020+
-->
1021+
1022+
* `path` {string|Buffer|URL|FileHandle} filename or `FileHandle`
1023+
* `options` {Object|string}
1024+
* `encoding` {string|null} **Default:** `'utf8'`
1025+
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
1026+
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
1027+
* `signal` {AbortSignal} allows aborting an in-progress readFile
1028+
* Returns: {Promise} Fulfills with the contents of the file parsed with
1029+
[`JSON.parse`][].
1030+
1031+
Asynchronously reads the contents of a file and call [`JSON.parse`][] on it.
1032+
1033+
The `path` parameter, and the `encoding`, `flag` and `signal` options are passed
1034+
to the underlying [`fsPromises.readFile()`][] call and the `reviver` option is
1035+
passed to [`JSON.parse`][].
1036+
1037+
If `options` is a string, then it specifies the encoding.
1038+
1039+
It is possible to abort an ongoing `readJSON` using an {AbortSignal}. If a
1040+
request is aborted the promise returned is rejected with an `AbortError`.
1041+
1042+
Any specified {FileHandle} has to support reading.
1043+
1044+
```mjs
1045+
import { readJSON } from 'fs/promises';
1046+
1047+
const json = await readJSON('package.json');
1048+
```
1049+
9901050
### `fsPromises.readlink(path[, options])`
9911051
<!-- YAML
9921052
added: v10.0.0
@@ -2952,6 +3012,35 @@ The Node.js GitHub issue [#25741][] provides more information and a detailed
29523012
analysis on the performance of `fs.readFile()` for multiple file sizes in
29533013
different Node.js versions.
29543014

3015+
### `fs.readJSON(path[, options], callback)`
3016+
<!-- YAML
3017+
added: REPLACEME
3018+
-->
3019+
3020+
* `path` {string|Buffer|URL|integer} filename or file descriptor
3021+
* `options` {Object|string}
3022+
* `encoding` {string|null} **Default:** `'utf8'`
3023+
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
3024+
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
3025+
* `signal` {AbortSignal} allows aborting an in-progress readJSON
3026+
* `callback` {Function}
3027+
* `err` {Error|AggregateError}
3028+
* `data` {any}
3029+
3030+
Asynchronously reads the contents of a file and call [`JSON.parse`][] on it.
3031+
3032+
The `path` parameter, and the `encoding`, `flag` and `signal` options are passed
3033+
to the underlying [`fs.readFileSync()`][] call and the `reviver` option is
3034+
passed to to [`JSON.parse`][].
3035+
3036+
For detailed information, see the documentation of [`fs.readFile()`][].
3037+
3038+
```mjs
3039+
import { readJSON } from 'fs';
3040+
3041+
readJSON('package.json', callback);
3042+
```
3043+
29553044
### `fs.readlink(path[, options], callback)`
29563045
<!-- YAML
29573046
added: v0.1.31
@@ -4618,6 +4707,33 @@ readFileSync('<directory>');
46184707
readFileSync('<directory>'); // => <data>
46194708
```
46204709
4710+
### `fs.readJSONSync(path[, options])`
4711+
<!-- YAML
4712+
added: REPLACEME
4713+
-->
4714+
4715+
* `path` {string|Buffer|URL|integer} filename or file descriptor
4716+
* `options` {Object|string}
4717+
* `encoding` {string|null} **Default:** `'utf8'`
4718+
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
4719+
* `reviver` {Function} Reviver function passed to [`JSON.parse`][].
4720+
* Returns: {any}
4721+
4722+
Reads the contents of the `path` and returns the result of calling
4723+
[`JSON.parse`][] on it.
4724+
4725+
The `path` parameter, and the `encoding` and `flag` options are passed to the
4726+
underlying [`fs.readFileSync()`][] call and the `reviver` option is passed to
4727+
to [`JSON.parse`][].
4728+
4729+
For detailed information, see the documentation of [`fs.readFile()`][].
4730+
4731+
```mjs
4732+
import { readJSONSync } from 'fs';
4733+
4734+
const json = readJSONSync('package.json');
4735+
```
4736+
46214737
### `fs.readlinkSync(path[, options])`
46224738
<!-- YAML
46234739
added: v0.1.31
@@ -6659,6 +6775,7 @@ the file contents.
66596775
[`AHAFS`]: https://www.ibm.com/developerworks/aix/library/au-aix_event_infrastructure/
66606776
[`Buffer.byteLength`]: buffer.md#buffer_static_method_buffer_bytelength_string_encoding
66616777
[`FSEvents`]: https://developer.apple.com/documentation/coreservices/file_system_events
6778+
[`JSON.Parse`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
66626779
[`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
66636780
[`ReadDirectoryChangesW`]: https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-readdirectorychangesw
66646781
[`UV_THREADPOOL_SIZE`]: cli.md#cli_uv_threadpool_size_size
@@ -6701,6 +6818,7 @@ the file contents.
67016818
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
67026819
[`fsPromises.open()`]: #fs_fspromises_open_path_flags_mode
67036820
[`fsPromises.opendir()`]: #fs_fspromises_opendir_path_options
6821+
[`fsPromises.readFile()`]: #fs_fspromises_readfile_path_options
67046822
[`fsPromises.rm()`]: #fs_fspromises_rm_path_options
67056823
[`fsPromises.utimes()`]: #fs_fspromises_utimes_path_atime_mtime
67066824
[`inotify(7)`]: https://man7.org/linux/man-pages/man7/inotify.7.html

lib/fs.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const kIoMaxLength = 2 ** 31 - 1;
3535
const {
3636
ArrayPrototypePush,
3737
BigIntPrototypeToString,
38+
JSONParse,
3839
MathMax,
3940
Number,
4041
ObjectCreate,
@@ -360,6 +361,27 @@ function readFile(path, options, callback) {
360361
req);
361362
}
362363

364+
function readJSON(path, options, callback) {
365+
callback = maybeCallback(callback || options);
366+
options = getOptions(options, { encoding: 'utf8', flag: 'r' });
367+
368+
const { reviver } = options;
369+
if (typeof reviver !== 'undefined') {
370+
validateFunction(reviver, 'options.reviver');
371+
}
372+
373+
readFile(path, options, (readFileError, file) => {
374+
if (readFileError) return callback(readFileError);
375+
let json;
376+
try {
377+
json = JSONParse(file, reviver);
378+
} catch (parseError) {
379+
return callback(parseError);
380+
}
381+
callback(null, json);
382+
});
383+
}
384+
363385
function tryStatSync(fd, isUserFd) {
364386
const ctx = {};
365387
const stats = binding.fstat(fd, false, undefined, ctx);
@@ -448,6 +470,17 @@ function readFileSync(path, options) {
448470
return buffer;
449471
}
450472

473+
function readJSONSync(path, options) {
474+
options = getOptions(options, { encoding: 'utf8', flag: 'r' });
475+
476+
const { reviver } = options;
477+
if (typeof reviver !== 'undefined') {
478+
validateFunction(reviver, 'options.reviver');
479+
}
480+
481+
return JSONParse(readFileSync(path, options), reviver);
482+
}
483+
451484
function defaultCloseCallback(err) {
452485
if (err != null) throw err;
453486
}
@@ -2157,6 +2190,8 @@ module.exports = fs = {
21572190
readvSync,
21582191
readFile,
21592192
readFileSync,
2193+
readJSON,
2194+
readJSONSync,
21602195
readlink,
21612196
readlinkSync,
21622197
realpath,

lib/internal/fs/promises.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const kWriteFileMaxChunkSize = 512 * 1024;
1111
const {
1212
ArrayPrototypePush,
1313
Error,
14+
JSONParse,
1415
MathMax,
1516
MathMin,
1617
NumberIsSafeInteger,
@@ -71,6 +72,7 @@ const {
7172
validateAbortSignal,
7273
validateBoolean,
7374
validateBuffer,
75+
validateFunction,
7476
validateInteger,
7577
validateUint32
7678
} = require('internal/validators');
@@ -146,6 +148,10 @@ class FileHandle extends EventEmitterMixin(JSTransferable) {
146148
return fsCall(readFile, this, options);
147149
}
148150

151+
readJSON(options) {
152+
return fsCall(readJSON, this, options);
153+
}
154+
149155
stat(options) {
150156
return fsCall(fstat, this, options);
151157
}
@@ -718,6 +724,18 @@ async function readFile(path, options) {
718724
return PromisePrototypeFinally(readFileHandle(fd, options), fd.close);
719725
}
720726

727+
async function readJSON(path, options) {
728+
options = getOptions(options, { encoding: 'utf8', flag: 'r' });
729+
730+
const { reviver } = options;
731+
if (typeof reviver !== 'undefined') {
732+
validateFunction(reviver, 'options.reviver');
733+
}
734+
735+
const file = await readFile(path, options);
736+
return JSONParse(file, reviver);
737+
}
738+
721739
module.exports = {
722740
exports: {
723741
access,
@@ -747,6 +765,7 @@ module.exports = {
747765
writeFile,
748766
appendFile,
749767
readFile,
768+
readJSON,
750769
watch,
751770
},
752771

lib/internal/source_map/source_map_cache.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ function lineLengths(content) {
126126

127127
function sourceMapFromFile(mapURL) {
128128
try {
129-
const content = fs.readFileSync(fileURLToPath(mapURL), 'utf8');
130-
const data = JSONParse(content);
129+
const data = fs.readJSONSync(fileURLToPath(mapURL));
131130
return sourcesToAbsolute(mapURL, data);
132131
} catch (err) {
133132
debug(err.stack);

test/common/wpt.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ class StatusLoader {
261261
load() {
262262
const dir = path.join(__dirname, '..', 'wpt');
263263
const statusFile = path.join(dir, 'status', `${this.path}.json`);
264-
const result = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
264+
const result = fs.readJSONSync(statusFile);
265265
this.rules.addRules(result);
266266

267267
const subDir = fixtures.path('wpt', this.path);

test/internet/test-trace-events-dns.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ for (const tr in tests) {
5959

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

62-
const data = fs.readFileSync(file);
63-
const traces = JSON.parse(data.toString()).traceEvents
62+
const traces = fs.readJSONSync(file).traceEvents
6463
.filter((trace) => trace.cat !== '__metadata');
6564

6665
assert(traces.length > 0);

0 commit comments

Comments
 (0)