1 | # json-ext
|
---|
2 |
|
---|
3 | [![NPM version](https://img.shields.io/npm/v/@discoveryjs/json-ext.svg)](https://www.npmjs.com/package/@discoveryjs/json-ext)
|
---|
4 | [![Build Status](https://travis-ci.org/discoveryjs/json-ext.svg?branch=master)](https://travis-ci.org/discoveryjs/json-ext)
|
---|
5 | [![Coverage Status](https://coveralls.io/repos/github/discoveryjs/json-ext/badge.svg?branch=master)](https://coveralls.io/github/discoveryjs/json-ext?)
|
---|
6 | [![NPM Downloads](https://img.shields.io/npm/dm/@discoveryjs/json-ext.svg)](https://www.npmjs.com/package/@discoveryjs/json-ext)
|
---|
7 |
|
---|
8 | A set of utilities that extend the use of JSON. Designed to be fast and memory efficient
|
---|
9 |
|
---|
10 | Features:
|
---|
11 |
|
---|
12 | - [x] `parseChunked()` – Parse JSON that comes by chunks (e.g. FS readable stream or fetch response stream)
|
---|
13 | - [x] `stringifyStream()` – Stringify stream (Node.js)
|
---|
14 | - [x] `stringifyInfo()` – Get estimated size and other facts of JSON.stringify() without converting a value to string
|
---|
15 | - [ ] **TBD** Support for circular references
|
---|
16 | - [ ] **TBD** Binary representation [branch](https://github.com/discoveryjs/json-ext/tree/binary)
|
---|
17 | - [ ] **TBD** WHATWG [Streams](https://streams.spec.whatwg.org/) support
|
---|
18 |
|
---|
19 | ## Install
|
---|
20 |
|
---|
21 | ```bash
|
---|
22 | npm install @discoveryjs/json-ext
|
---|
23 | ```
|
---|
24 |
|
---|
25 | ## API
|
---|
26 |
|
---|
27 | - [parseChunked(chunkEmitter)](#parsechunkedchunkemitter)
|
---|
28 | - [stringifyStream(value[, replacer[, space]])](#stringifystreamvalue-replacer-space)
|
---|
29 | - [stringifyInfo(value[, replacer[, space[, options]]])](#stringifyinfovalue-replacer-space-options)
|
---|
30 | - [Options](#options)
|
---|
31 | - [async](#async)
|
---|
32 | - [continueOnCircular](#continueoncircular)
|
---|
33 | - [version](#version)
|
---|
34 |
|
---|
35 | ### parseChunked(chunkEmitter)
|
---|
36 |
|
---|
37 | Works the same as [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) but takes `chunkEmitter` instead of string and returns [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
---|
38 |
|
---|
39 | > NOTE: `reviver` parameter is not supported yet, but will be added in next releases.
|
---|
40 | > NOTE: WHATWG streams aren't supported yet
|
---|
41 |
|
---|
42 | When to use:
|
---|
43 | - It's required to avoid freezing the main thread during big JSON parsing, since this process can be distributed in time
|
---|
44 | - Huge JSON needs to be parsed (e.g. >500MB on Node.js)
|
---|
45 | - Needed to reduce memory pressure. `JSON.parse()` needs to receive the entire JSON before parsing it. With `parseChunked()` you may parse JSON as first bytes of it comes. This approach helps to avoid storing a huge string in the memory at a single time point and following GC.
|
---|
46 |
|
---|
47 | [Benchmark](https://github.com/discoveryjs/json-ext/tree/master/benchmarks#parse-chunked)
|
---|
48 |
|
---|
49 | Usage:
|
---|
50 |
|
---|
51 | ```js
|
---|
52 | const { parseChunked } = require('@discoveryjs/json-ext');
|
---|
53 |
|
---|
54 | // as a regular Promise
|
---|
55 | parseChunked(chunkEmitter)
|
---|
56 | .then(data => {
|
---|
57 | /* data is parsed JSON */
|
---|
58 | });
|
---|
59 |
|
---|
60 | // using await (keep in mind that not every runtime has a support for top level await)
|
---|
61 | const data = await parseChunked(chunkEmitter);
|
---|
62 | ```
|
---|
63 |
|
---|
64 | Parameter `chunkEmitter` can be:
|
---|
65 | - [`ReadableStream`](https://nodejs.org/dist/latest-v14.x/docs/api/stream.html#stream_readable_streams) (Node.js only)
|
---|
66 | ```js
|
---|
67 | const fs = require('fs');
|
---|
68 | const { parseChunked } = require('@discoveryjs/json-ext');
|
---|
69 |
|
---|
70 | parseChunked(fs.createReadStream('path/to/file.json'))
|
---|
71 | ```
|
---|
72 | - Generator, async generator or function that returns iterable (chunks). Chunk might be a `string`, `Uint8Array` or `Buffer` (Node.js only):
|
---|
73 | ```js
|
---|
74 | const { parseChunked } = require('@discoveryjs/json-ext');
|
---|
75 | const encoder = new TextEncoder();
|
---|
76 |
|
---|
77 | // generator
|
---|
78 | parseChunked(function*() {
|
---|
79 | yield '{ "hello":';
|
---|
80 | yield Buffer.from(' "wor'); // Node.js only
|
---|
81 | yield encoder.encode('ld" }'); // returns Uint8Array(5) [ 108, 100, 34, 32, 125 ]
|
---|
82 | });
|
---|
83 |
|
---|
84 | // async generator
|
---|
85 | parseChunked(async function*() {
|
---|
86 | for await (const chunk of someAsyncSource) {
|
---|
87 | yield chunk;
|
---|
88 | }
|
---|
89 | });
|
---|
90 |
|
---|
91 | // function that returns iterable
|
---|
92 | parseChunked(() => ['{ "hello":', ' "world"}'])
|
---|
93 | ```
|
---|
94 |
|
---|
95 | Using with [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API):
|
---|
96 |
|
---|
97 | ```js
|
---|
98 | async function loadData(url) {
|
---|
99 | const response = await fetch(url);
|
---|
100 | const reader = response.body.getReader();
|
---|
101 |
|
---|
102 | return parseChunked(async function*() {
|
---|
103 | while (true) {
|
---|
104 | const { done, value } = await reader.read();
|
---|
105 |
|
---|
106 | if (done) {
|
---|
107 | break;
|
---|
108 | }
|
---|
109 |
|
---|
110 | yield value;
|
---|
111 | }
|
---|
112 | });
|
---|
113 | }
|
---|
114 |
|
---|
115 | loadData('https://example.com/data.json')
|
---|
116 | .then(data => {
|
---|
117 | /* data is parsed JSON */
|
---|
118 | })
|
---|
119 | ```
|
---|
120 |
|
---|
121 | ### stringifyStream(value[, replacer[, space]])
|
---|
122 |
|
---|
123 | Works the same as [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify), but returns an instance of [`ReadableStream`](https://nodejs.org/dist/latest-v14.x/docs/api/stream.html#stream_readable_streams) instead of string.
|
---|
124 |
|
---|
125 | > NOTE: WHATWG Streams aren't supported yet, so function available for Node.js only for now
|
---|
126 |
|
---|
127 | Departs from JSON.stringify():
|
---|
128 | - Outputs `null` when `JSON.stringify()` returns `undefined` (since streams may not emit `undefined`)
|
---|
129 | - A promise is resolving and the resulting value is stringifying as a regular one
|
---|
130 | - A stream in non-object mode is piping to output as is
|
---|
131 | - A stream in object mode is piping to output as an array of objects
|
---|
132 |
|
---|
133 | When to use:
|
---|
134 | - Huge JSON needs to be generated (e.g. >500MB on Node.js)
|
---|
135 | - Needed to reduce memory pressure. `JSON.stringify()` needs to generate the entire JSON before send or write it to somewhere. With `stringifyStream()` you may send a result to somewhere as first bytes of the result appears. This approach helps to avoid storing a huge string in the memory at a single time point.
|
---|
136 | - The object being serialized contains Promises or Streams (see Usage for examples)
|
---|
137 |
|
---|
138 | [Benchmark](https://github.com/discoveryjs/json-ext/tree/master/benchmarks#stream-stringifying)
|
---|
139 |
|
---|
140 | Usage:
|
---|
141 |
|
---|
142 | ```js
|
---|
143 | const { stringifyStream } = require('@discoveryjs/json-ext');
|
---|
144 |
|
---|
145 | // handle events
|
---|
146 | stringifyStream(data)
|
---|
147 | .on('data', chunk => console.log(chunk))
|
---|
148 | .on('error', error => consold.error(error))
|
---|
149 | .on('finish', () => console.log('DONE!'));
|
---|
150 |
|
---|
151 | // pipe into a stream
|
---|
152 | stringifyStream(data)
|
---|
153 | .pipe(writableStream);
|
---|
154 | ```
|
---|
155 |
|
---|
156 | Using Promise or ReadableStream in serializing object:
|
---|
157 |
|
---|
158 | ```js
|
---|
159 | const fs = require('fs');
|
---|
160 | const { stringifyStream } = require('@discoveryjs/json-ext');
|
---|
161 |
|
---|
162 | // output will be
|
---|
163 | // {"name":"example","willSerializeResolvedValue":42,"fromFile":[1, 2, 3],"at":{"any":{"level":"promise!"}}}
|
---|
164 | stringifyStream({
|
---|
165 | name: 'example',
|
---|
166 | willSerializeResolvedValue: Promise.resolve(42),
|
---|
167 | fromFile: fs.createReadStream('path/to/file.json'), // support file content is "[1, 2, 3]", it'll be inserted as it
|
---|
168 | at: {
|
---|
169 | any: {
|
---|
170 | level: new Promise(resolve => setTimeout(() => resolve('promise!'), 100))
|
---|
171 | }
|
---|
172 | }
|
---|
173 | })
|
---|
174 |
|
---|
175 | // in case several async requests are used in object, it's prefered
|
---|
176 | // to put fastest requests first, because in this case
|
---|
177 | stringifyStream({
|
---|
178 | foo: fetch('http://example.com/request_takes_2s').then(req => req.json()),
|
---|
179 | bar: fetch('http://example.com/request_takes_5s').then(req => req.json())
|
---|
180 | });
|
---|
181 | ```
|
---|
182 |
|
---|
183 | Using with [`WritableStream`](https://nodejs.org/dist/latest-v14.x/docs/api/stream.html#stream_writable_streams) (Node.js only):
|
---|
184 |
|
---|
185 | ```js
|
---|
186 | const fs = require('fs');
|
---|
187 | const { stringifyStream } = require('@discoveryjs/json-ext');
|
---|
188 |
|
---|
189 | // pipe into a console
|
---|
190 | stringifyStream(data)
|
---|
191 | .pipe(process.stdout);
|
---|
192 |
|
---|
193 | // pipe into a file
|
---|
194 | stringifyStream(data)
|
---|
195 | .pipe(fs.createWriteStream('path/to/file.json'));
|
---|
196 |
|
---|
197 | // wrapping into a Promise
|
---|
198 | new Promise((resolve, reject) => {
|
---|
199 | stringifyStream(data)
|
---|
200 | .on('error', reject)
|
---|
201 | .pipe(stream)
|
---|
202 | .on('error', reject)
|
---|
203 | .on('finish', resolve);
|
---|
204 | });
|
---|
205 | ```
|
---|
206 |
|
---|
207 | ### stringifyInfo(value[, replacer[, space[, options]]])
|
---|
208 |
|
---|
209 | `value`, `replacer` and `space` arguments are the same as for `JSON.stringify()`.
|
---|
210 |
|
---|
211 | Result is an object:
|
---|
212 |
|
---|
213 | ```js
|
---|
214 | {
|
---|
215 | minLength: Number, // minimal bytes when values is stringified
|
---|
216 | circular: [...], // list of circular references
|
---|
217 | duplicate: [...], // list of objects that occur more than once
|
---|
218 | async: [...] // list of async values, i.e. promises and streams
|
---|
219 | }
|
---|
220 | ```
|
---|
221 |
|
---|
222 | Example:
|
---|
223 |
|
---|
224 | ```js
|
---|
225 | const { stringifyInfo } = require('@discoveryjs/json-ext');
|
---|
226 |
|
---|
227 | console.log(
|
---|
228 | stringifyInfo({ test: true }).minLength
|
---|
229 | );
|
---|
230 | // > 13
|
---|
231 | // that equals '{"test":true}'.length
|
---|
232 | ```
|
---|
233 |
|
---|
234 | #### Options
|
---|
235 |
|
---|
236 | ##### async
|
---|
237 |
|
---|
238 | Type: `Boolean`
|
---|
239 | Default: `false`
|
---|
240 |
|
---|
241 | Collect async values (promises and streams) or not.
|
---|
242 |
|
---|
243 | ##### continueOnCircular
|
---|
244 |
|
---|
245 | Type: `Boolean`
|
---|
246 | Default: `false`
|
---|
247 |
|
---|
248 | Stop collecting info for a value or not whenever circular reference is found. Setting option to `true` allows to find all circular references.
|
---|
249 |
|
---|
250 | ### version
|
---|
251 |
|
---|
252 | The version of library, e.g. `"0.3.1"`.
|
---|
253 |
|
---|
254 | ## License
|
---|
255 |
|
---|
256 | MIT
|
---|