├── .eslintrc
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── index.js
├── package.json
├── test
├── big-json-with-string.txt
├── test-index-of-generator.js
├── test-parse-async.js
├── test-parse-basic.js
├── test-parse-big-escaped-string.js
├── test-parse-exponential.js
├── test-parse-intensity.js
├── test-parse-nestedquotes.js
├── test-parse-params.js
├── test-parse-reviver.js
├── test-stringify-async.js
├── test-stringify-basic.js
├── test-stringify-intensity.js
├── test-stringify-nestedquotes.js
├── test-stringify-params.js
├── test-stringify-replacer.js
├── test-stringify-same-object-twice.js
├── test-stringify-space.js
├── test.js
├── test_nested_parse.js
└── test_nested_stringify.js
├── utils
└── index-of-generator.js
├── yieldable-parser.js
└── yieldable-stringify.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "strongloop",
3 | "parserOptions": {
4 | "ecmaVersion": 6
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to yieldable-json
2 | Thanks for your interest in this project. Please refer to [https://github.com/ibmruntimes/yieldable-json](https://github.com/ibmruntimes/yieldable-json)
3 | for details on the project description and the objective. Long term vision
4 | of the project is to collaborate with a large group of developers,
5 | implement and incorporate projects of similar objectives under this umbrella.
6 |
7 | ## Submitting a contribution
8 | You can propose contributions by sending pull requests through GitHub. Following these guidelines
9 | will help us to merge your pull requests smoothly:
10 |
11 | 1. It is generally a good idea to file an issue to explain your idea before
12 | writing code or submitting a pull request, though this is not mandatory.
13 | 2. Please read carefully and adhere to the legal considerations and
14 | copyright/license requirements outlined below.
15 | 3. Follow the coding style and format of the code you are modifying as the existing code base.
16 | The existing code base was built using eslint and eslint-config-strongloop (see package.json).
17 | 4. Follow the commit guidelines found below.
18 | 5. Ensure that `npm run pretest` and `npm run test` pass all tests before you submit a Pull Request.
19 |
20 | ## Commit Guidelines
21 | Start the first line with the area of code or the feature which gets affected,
22 | followed by a phrase or a fully formed sentence which describes the change made.
23 | It is written in the imperative mood, say what happens when the patch is applied.
24 | Keep it short and simple. The first line should be less than 50 characters,
25 | sentence case, and does not end in a period. Leave a blank line between the
26 | first line and the message body.
27 |
28 | An example of the first line is:
29 | `parser: remove user code from being evaluated`
30 |
31 | The body should be wrapped at 72 characters, where reasonable.
32 |
33 | Include as much information in your commit as possible. You may want to include
34 | designs and rationale, examples and code, or issues and next steps. Prefer
35 | copying resources into the body of the commit over providing external links.
36 | Structure large commit messages with headers, references etc. Remember however
37 | that the commit message is always going to be rendered in plain text.
38 |
39 | It is important that you read and understand the legal considerations found
40 | below when signing off or contributing any commit.
41 |
42 | ## Copyright Notice and Licensing Requirements
43 | **It is the responsibility of each contributor to obtain legal advice, and
44 | to ensure that their contributions fulfill the legal requirements of their
45 | organization. This document is not legal advice.**
46 |
47 | yieldable-json is licensed under the Apache License v2.0. Any previously
48 | unlicensed contribution should be released under the same license.
49 |
50 | * If you wish to contribute code under a different license, you must consult
51 | with a committer before contributing.
52 | * For any scenario not covered by this document, please discuss the copyright
53 | notice and licensing requirements with a committer before contributing.
54 |
55 | The template for the copyright notice and license is as follows:
56 | ```c
57 | /*******************************************************************************
58 | *
59 | *
60 | *
61 | * This program and the accompanying materials are made available
62 | * under the terms of the Apache License v2.0 which accompanies
63 | * this distribution.
64 | *
65 | * The Apache License v2.0 is available at
66 | * http://www.opensource.org/licenses/apache2.0.php
67 | *
68 | * Contributors:
69 | * -
70 | *******************************************************************************/
71 | ```
72 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Product: yieldable-json
2 |
3 | (c) Copyright IBM Corp. 2017 All rights reserved.
4 |
5 | This program and the accompanying materials are licensed under
6 | the terms of Apache v2.0 License which are reproduced below.
7 | You may distribute this program and materials under the Apache V2.0 License
8 |
9 | Apache License, Version 2.0
10 |
11 | Apache License
12 | Version 2.0, January 2004
13 | http://www.apache.org/licenses/
14 |
15 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
16 |
17 | 1. Definitions.
18 |
19 | "License" shall mean the terms and conditions for use, reproduction,
20 | and distribution as defined by Sections 1 through 9 of this document.
21 |
22 | "Licensor" shall mean the copyright owner or entity authorized by
23 | the copyright owner that is granting the License.
24 |
25 | "Legal Entity" shall mean the union of the acting entity and all
26 | other entities that control, are controlled by, or are under common
27 | control with that entity. For the purposes of this definition,
28 | "control" means (i) the power, direct or indirect, to cause the
29 | direction or management of such entity, whether by contract or
30 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
31 | outstanding shares, or (iii) beneficial ownership of such entity.
32 |
33 | "You" (or "Your") shall mean an individual or Legal Entity
34 | exercising permissions granted by this License.
35 |
36 | "Source" form shall mean the preferred form for making modifications,
37 | including but not limited to software source code, documentation
38 | source, and configuration files.
39 |
40 | "Object" form shall mean any form resulting from mechanical
41 | transformation or translation of a Source form, including but
42 | not limited to compiled object code, generated documentation,
43 | and conversions to other media types.
44 |
45 | "Work" shall mean the work of authorship, whether in Source or
46 | Object form, made available under the License, as indicated by a
47 | copyright notice that is included in or attached to the work
48 | (an example is provided in the Appendix below).
49 |
50 | "Derivative Works" shall mean any work, whether in Source or Object
51 | form, that is based on (or derived from) the Work and for which the
52 | editorial revisions, annotations, elaborations, or other modifications
53 | represent, as a whole, an original work of authorship. For the purposes
54 | of this License, Derivative Works shall not include works that remain
55 | separable from, or merely link (or bind by name) to the interfaces of,
56 | the Work and Derivative Works thereof.
57 |
58 | "Contribution" shall mean any work of authorship, including
59 | the original version of the Work and any modifications or additions
60 | to that Work or Derivative Works thereof, that is intentionally
61 | submitted to Licensor for inclusion in the Work by the copyright owner
62 | or by an individual or Legal Entity authorized to submit on behalf of
63 | the copyright owner. For the purposes of this definition, "submitted"
64 | means any form of electronic, verbal, or written communication sent
65 | to the Licensor or its representatives, including but not limited to
66 | communication on electronic mailing lists, source code control systems,
67 | and issue tracking systems that are managed by, or on behalf of, the
68 | Licensor for the purpose of discussing and improving the Work, but
69 | excluding communication that is conspicuously marked or otherwise
70 | designated in writing by the copyright owner as "Not a Contribution."
71 |
72 | "Contributor" shall mean Licensor and any individual or Legal Entity
73 | on behalf of whom a Contribution has been received by Licensor and
74 | subsequently incorporated within the Work.
75 |
76 | 2. Grant of Copyright License. Subject to the terms and conditions of
77 | this License, each Contributor hereby grants to You a perpetual,
78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
79 | copyright license to reproduce, prepare Derivative Works of,
80 | publicly display, publicly perform, sublicense, and distribute the
81 | Work and such Derivative Works in Source or Object form.
82 |
83 | 3. Grant of Patent License. Subject to the terms and conditions of
84 | this License, each Contributor hereby grants to You a perpetual,
85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
86 | (except as stated in this section) patent license to make, have made,
87 | use, offer to sell, sell, import, and otherwise transfer the Work,
88 | where such license applies only to those patent claims licensable
89 | by such Contributor that are necessarily infringed by their
90 | Contribution(s) alone or by combination of their Contribution(s)
91 | with the Work to which such Contribution(s) was submitted. If You
92 | institute patent litigation against any entity (including a
93 | cross-claim or counterclaim in a lawsuit) alleging that the Work
94 | or a Contribution incorporated within the Work constitutes direct
95 | or contributory patent infringement, then any patent licenses
96 | granted to You under this License for that Work shall terminate
97 | as of the date such litigation is filed.
98 |
99 | 4. Redistribution. You may reproduce and distribute copies of the
100 | Work or Derivative Works thereof in any medium, with or without
101 | modifications, and in Source or Object form, provided that You
102 | meet the following conditions:
103 |
104 | (a) You must give any other recipients of the Work or
105 | Derivative Works a copy of this License; and
106 |
107 | (b) You must cause any modified files to carry prominent notices
108 | stating that You changed the files; and
109 |
110 | (c) You must retain, in the Source form of any Derivative Works
111 | that You distribute, all copyright, patent, trademark, and
112 | attribution notices from the Source form of the Work,
113 | excluding those notices that do not pertain to any part of
114 | the Derivative Works; and
115 |
116 | (d) If the Work includes a "NOTICE" text file as part of its
117 | distribution, then any Derivative Works that You distribute must
118 | include a readable copy of the attribution notices contained
119 | within such NOTICE file, excluding those notices that do not
120 | pertain to any part of the Derivative Works, in at least one
121 | of the following places: within a NOTICE text file distributed
122 | as part of the Derivative Works; within the Source form or
123 | documentation, if provided along with the Derivative Works; or,
124 | within a display generated by the Derivative Works, if and
125 | wherever such third-party notices normally appear. The contents
126 | of the NOTICE file are for informational purposes only and
127 | do not modify the License. You may add Your own attribution
128 | notices within Derivative Works that You distribute, alongside
129 | or as an addendum to the NOTICE text from the Work, provided
130 | that such additional attribution notices cannot be construed
131 | as modifying the License.
132 |
133 | You may add Your own copyright statement to Your modifications and
134 | may provide additional or different license terms and conditions
135 | for use, reproduction, or distribution of Your modifications, or
136 | for any such Derivative Works as a whole, provided Your use,
137 | reproduction, and distribution of the Work otherwise complies with
138 | the conditions stated in this License.
139 |
140 | 5. Submission of Contributions. Unless You explicitly state otherwise,
141 | any Contribution intentionally submitted for inclusion in the Work
142 | by You to the Licensor shall be under the terms and conditions of
143 | this License, without any additional terms or conditions.
144 | Notwithstanding the above, nothing herein shall supersede or modify
145 | the terms of any separate license agreement you may have executed
146 | with Licensor regarding such Contributions.
147 |
148 | 6. Trademarks. This License does not grant permission to use the trade
149 | names, trademarks, service marks, or product names of the Licensor,
150 | except as required for reasonable and customary use in describing the
151 | origin of the Work and reproducing the content of the NOTICE file.
152 |
153 | 7. Disclaimer of Warranty. Unless required by applicable law or
154 | agreed to in writing, Licensor provides the Work (and each
155 | Contributor provides its Contributions) on an "AS IS" BASIS,
156 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
157 | implied, including, without limitation, any warranties or conditions
158 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
159 | PARTICULAR PURPOSE. You are solely responsible for determining the
160 | appropriateness of using or redistributing the Work and assume any
161 | risks associated with Your exercise of permissions under this License.
162 |
163 | 8. Limitation of Liability. In no event and under no legal theory,
164 | whether in tort (including negligence), contract, or otherwise,
165 | unless required by applicable law (such as deliberate and grossly
166 | negligent acts) or agreed to in writing, shall any Contributor be
167 | liable to You for damages, including any direct, indirect, special,
168 | incidental, or consequential damages of any character arising as a
169 | result of this License or out of the use or inability to use the
170 | Work (including but not limited to damages for loss of goodwill,
171 | work stoppage, computer failure or malfunction, or any and all
172 | other commercial damages or losses), even if such Contributor
173 | has been advised of the possibility of such damages.
174 |
175 | 9. Accepting Warranty or Additional Liability. While redistributing
176 | the Work or Derivative Works thereof, You may choose to offer,
177 | and charge a fee for, acceptance of support, warranty, indemnity,
178 | or other liability obligations and/or rights consistent with this
179 | License. However, in accepting such obligations, You may act only
180 | on Your own behalf and on Your sole responsibility, not on behalf
181 | of any other Contributor, and only if You agree to indemnify,
182 | defend, and hold each Contributor harmless for any liability
183 | incurred by, or claims asserted against, such Contributor by reason
184 | of your accepting any such warranty or additional liability.
185 |
186 | END OF TERMS AND CONDITIONS
187 | C. The ISC License
188 | Copyright (c) Isaac Z. Schlueter and Contributors
189 | Permission to use, copy, modify, and/or distribute this software for any
190 | purpose with or without fee is hereby granted, provided that the above
191 | copyright notice and this permission notice appear in all copies.
192 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
193 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
194 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
195 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
196 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
197 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
198 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
199 | D. Copyright JS Foundation and other contributors, https://js.foundation
200 | Permission is hereby granted, free of charge, to any person obtaining a copy
201 | of this software and associated documentation files (the "Software"), to deal
202 | in the Software without restriction, including without limitation the rights
203 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
204 | copies of the Software, and to permit persons to whom the Software is
205 | furnished to do so, subject to the following conditions:
206 | The above copyright notice and this permission notice shall be included in
207 | all copies or substantial portions of the Software.
208 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
209 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
210 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
211 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
212 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
213 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
214 | THE SOFTWARE.
215 |
216 | E. Copyright (c) IBM Corp. 2015,2016. All Rights Reserved.
217 | Node module: eslint-config-strongloop
218 | This project is licensed under the Artistic License 2.0, full text below.
219 |
220 | The Artistic License 2.0
221 | Copyright (c) 2000-2006, The Perl Foundation.
222 | Everyone is permitted to copy and distribute verbatim copies of this license
223 | document, but changing it is not allowed.
224 | Preamble
225 | This license establishes the terms under which a given free software Package
226 | may be copied, modified, distributed, and/or redistributed. The intent is that
227 | the Copyright Holder maintains some artistic control over the development of
228 | that Package while still keeping the Package available as open source and
229 | free software.
230 | You are always permitted to make arrangements wholly outside of this license
231 | directly with the Copyright Holder of a given Package. If the terms of this
232 | license do not permit the full use that you propose to make of the Package,
233 | you should contact the Copyright Holder and seek a different licensing arrangement.
234 | Definitions
235 | "Copyright Holder" means the individual(s) or organization(s)
236 | named in the copyright notice for the entire Package.
237 |
238 | "Contributor" means any party that has contributed code or other
239 | material to the Package, in accordance with the Copyright Holder's
240 | procedures.
241 |
242 | "You" and "your" means any person who would like to copy,
243 | distribute, or modify the Package.
244 |
245 | "Package" means the collection of files distributed by the
246 | Copyright Holder, and derivatives of that collection and/or of
247 | those files. A given Package may consist of either the Standard
248 | Version, or a Modified Version.
249 |
250 | "Distribute" means providing a copy of the Package or making it
251 | accessible to anyone else, or in the case of a company or
252 | organization, to others outside of your company or organization.
253 |
254 | "Distributor Fee" means any fee that you charge for Distributing
255 | this Package or providing support for this Package to another
256 | party. It does not mean licensing fees.
257 |
258 | "Standard Version" refers to the Package if it has not been
259 | modified, or has been modified only in ways explicitly requested
260 | by the Copyright Holder.
261 |
262 | "Modified Version" means the Package, if it has been changed, and
263 | such changes were not explicitly requested by the Copyright
264 | Holder.
265 |
266 | "Original License" means this Artistic License as Distributed with
267 | the Standard Version of the Package, in its current version or as
268 | it may be modified by The Perl Foundation in the future.
269 |
270 | "Source" form means the source code, documentation source, and
271 | configuration files for the Package.
272 |
273 | "Compiled" form means the compiled bytecode, object code, binary,
274 | or any other form resulting from mechanical transformation or
275 | translation of the Source form.
276 | Permission for Use and Modification Without Distribution
277 | (1) You are permitted to use the Standard Version and create and use
278 | Modified Versions for any purpose without restriction, provided that
279 | you do not Distribute the Modified Version.
280 | Permissions for Redistribution of the Standard Version
281 | (2) You may Distribute verbatim copies of the Source form of the
282 | Standard Version of this Package in any medium without restriction,
283 | either gratis or for a Distributor Fee, provided that you duplicate
284 | all of the original copyright notices and associated disclaimers.
285 | At your discretion, such verbatim copies may or may not
286 | include a Compiled form of the Package.
287 | (3) You may apply any bug fixes, portability changes, and other
288 | modifications made available from the Copyright Holder. The resulting
289 | Package will still be considered the Standard Version, and as such
290 | will be subject to the Original License.
291 | Distribution of Modified Versions of the Package as Source
292 | (4) You may Distribute your Modified Version as Source (either gratis
293 | or for a Distributor Fee, and with or without a Compiled form of the
294 | Modified Version) provided that you clearly document how it differs
295 | from the Standard Version, including, but not limited to, documenting
296 | any non-standard features, executables, or modules, and provided
297 | that you do at least ONE of the following:
298 | (a) make the Modified Version available to the Copyright Holder
299 | of the Standard Version, under the Original License, so that the
300 | Copyright Holder may include your modifications in the Standard
301 | Version.
302 |
303 | (b) ensure that installation of your Modified Version does not
304 | prevent the user installing or running the Standard Version. In
305 | addition, the Modified Version must bear a name that is different
306 | from the name of the Standard Version.
307 |
308 | (c) allow anyone who receives a copy of the Modified Version to
309 | make the Source form of the Modified Version available to others
310 | under
311 |
312 | (i) the Original License or
313 |
314 | (ii) a license that permits the licensee to freely copy,
315 | modify and redistribute the Modified Version using the same
316 | licensing terms that apply to the copy that the licensee
317 | received, and requires that the Source form of the Modified
318 | Version, and of any works derived from it, be made freely
319 | available in that license fees are prohibited but Distributor
320 | Fees are allowed.
321 | Distribution of Compiled Forms of the Standard Version or Modified
322 | Versions without the Source
323 | (5) You may Distribute Compiled forms of the Standard Version without
324 | the Source, provided that you include complete instructions on how to
325 | get the Source of the Standard Version. Such instructions must be
326 | valid at the time of your distribution. If these instructions, at any
327 | time while you are carrying out such distribution, become invalid,
328 | you must provide new instructions on demand or cease further
329 | distribution. If you provide valid instructions or cease distribution
330 | within thirty days after you become aware that the instructions are
331 | invalid, then you do not forfeit any of your rights under this license.
332 | (6) You may Distribute a Modified Version in Compiled form without the
333 | Source, provided that you comply with Section 4 with respect to the
334 | Source of the Modified Version.
335 | Aggregating or Linking the Package
336 | (7) You may aggregate the Package (either the Standard Version or
337 | Modified Version) with other packages and Distribute the resulting
338 | aggregation provided that you do not charge a licensing fee for the
339 | Package. Distributor Fees are permitted, and licensing fees for other
340 | components in the aggregation are permitted. The terms of this license
341 | apply to the use and Distribution of the Standard or Modified Versions
342 | as included in the aggregation.
343 | (8) You are permitted to link Modified and Standard Versions with other
344 | works, to embed the Package in a larger work of your own, or to build
345 | stand-alone binary or bytecode versions of applications that include
346 | the Package, and Distribute the result without restriction, provided
347 | the result does not expose a direct interface to the Package.
348 | Items That are Not Considered Part of a Modified Version
349 | (9) Works (including, but not limited to, modules and scripts) that
350 | merely extend or make use of the Package, do not, by themselves, cause
351 | the Package to be a Modified Version. In addition, such works are not
352 | considered parts of the Package itself, and are not subject to the
353 | terms of this license.
354 | General Provisions
355 | (10) Any use, modification, and distribution of the Standard or
356 | Modified Versions is governed by this Artistic License. By using,
357 | modifying or distributing the Package, you accept this license.
358 | Do not use, modify, or distribute the Package, if you do not accept this license.
359 | (11) If your Modified Version has been derived from a Modified Version
360 | made by someone other than you, you are nevertheless required to ensure
361 | that your Modified Version complies with the requirements of this license.
362 | (12) This license does not grant you the right to use any trademark,
363 | service mark, tradename, or logo of the Copyright Holder.
364 | (13) This license includes the non-exclusive, worldwide, free-of-charge
365 | patent license to make, have made, use, offer to sell, sell, import and
366 | otherwise transfer the Package with respect to any patent claims licensable
367 | by the Copyright Holder that are necessarily infringed by the Package. If
368 | you institute patent litigation (including a cross-claim or counterclaim)
369 | against any party alleging that the Package constitutes direct or
370 | contributory patent infringement, then this Artistic License to you shall
371 | terminate on the date that such litigation is filed.
372 | (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT
373 | HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED
374 | WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
375 | PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT
376 | PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT
377 | HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT,
378 | INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE
379 | USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
380 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Asynchronous JSON.parse and JSON.stringify APIs
6 |
7 |
8 | # **yieldable-json**
9 | This library provides asynchronous version of standard `JSON.parse` and `JSON.stringify` APIs.
10 |
11 | ### **Use Case**
12 | Node.js based web applications dealing with large JSON objects while requiring high level of concurrency for the transactions.
13 |
14 | Here '*largeness*' applies to any of, or all of, or any combination thereof deep compositions, large number of fields, and fields with large data (such as massive strings).
15 |
16 | In contrast to `JSON Streams`, which are built with custom stream oriented protocols (at library as well as application level) and used in special use cases like data analytics, monitors, etc., `yieldable-json` works on fully formed JSON data that is characterized only by object size and designed with an objective of improving concurrency in the application and thereby usable in any workload scenarios.
17 |
18 |
19 | ----------
20 |
21 |
22 | ### **Statistics**
23 | Testing with a wide range of data showed that objects which are bigger than `256KB` (when stringified) degrades concurrency in the application.
24 |
25 | While definition of concurrency is subjective, a number of tests we performed in short and long round-trip networks showed that being available in the event loop for attending to concurrent requests at every `5 milliseconds` provides the required responsiveness to clients, as well as meets the overall concurrency expectation on the application.
26 |
27 |
28 |
29 | Data Volume |
30 | Loop starvation (in milliseconds) |
31 |
32 |
33 | JSON (built-in) |
34 | yieldable-json |
35 |
36 |
37 | 115 KB |
38 | 2 |
39 | 5 |
40 |
41 |
42 | 327 KB |
43 | 10 |
44 | 5 |
45 |
46 |
47 | 1.3 MB |
48 | 50 |
49 | 5 |
50 |
51 |
52 | 2.2 MB |
53 | 100 |
54 | 5 |
55 |
56 |
57 |
58 | As shown in the table, the yieldable-json guarantees the event loop is reached and processed in every `5 ms`, irrespective of the JSON volume being processed.
59 |
60 |
61 | ----------
62 |
63 |
64 | ### **Background**
65 | In Cloud based deployments, we foresee increase of JSON payload across distributed computing end-points in an exponential manner, causing performance bottleneck in the single-threaded node.js platform, and hence this attempt. The key function of asynchronous JSON APIs are to perform marshalling and un-marshalling of massive data incrementally, and yielding back to the application occasionally, and reduce loop starvation.
66 |
67 | The ES6's generator function along with `yield` semantics is used for implementing incremental traversal. The extent of traversal in one iteration is controlled by the `intensity` parameter. While there are some `globally` accessible variables within main APIs, predominently the parser and stringifier is implemented through a self-recursing loop. The callback is guaranteed to fire no earlier than next tick to emulate the asynchronous behavior.
68 |
69 | Because of the usage of ES6 semantics, this module is supported only on `node v4.x and above`.
70 |
71 |
72 | --------
73 |
74 |
75 | ### **APIs**
76 | ***stringifyAsync***:
77 |
78 | stringifyAsync(value[, replacer][, space][, intensity], callback)
79 |
80 | * *`value`* <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> The javascript object which need to be converted to its equivalent JSON string.
81 |
82 | * *`replacer`* `Optional` <[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function)> | <[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)> As a function, it takes two parameters, the key and the value being stringified. The object in which the key was found is provided as the replacer's this parameter. Initially it gets called with an empty key representing the object being stringified, and it then gets called for each property on the object or array being stringified. It should return the value that should be added to the JSON string, as follows:
83 |
84 | 1. If you return a String, that string is used as the property's value when adding it to the JSON string.
85 | 2. If you return a Boolean, "true" or "false" is used as the property's value, as appropriate, when adding it to the JSON string.
86 | 3. If you return any other object, the object is recursively stringified into the JSON string, calling the replacer function on each property, unless the object is a function, in which case nothing is added to the JSON string.
87 | 4. If you return undefined, the property is not included (i.e., filtered out) in the output JSON string.
88 |
89 | * *`toJSON() behavior`* If an object being stringified has a property named `toJSON` whose value is a function, then the `toJSON()` method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the `toJSON()` method when called will be serialized.
90 |
91 | * *`space`* `Optional` <[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type)> | <[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type)> Used to insert white space into the output JSON string for readability purposes. If this is a Number, it indicates the number of space characters to use as white space; this number is capped at 10 (if it is greater, the value is just 10). Values less than 1 indicate that no space should be used. If this is a String, the string (or the first 10 characters of the string, if it's longer than that) is used as white space. If this parameter is not provided (or is null), no white space is used.
92 |
93 | * *`intensity`* `Optional` <[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type)> Takes the value between the range 1-32. This is used to compute the extent of traversal in the object tree in one iteration. The more the intensity, the more time will be spent in the stringify operation before it yields back to the uv loop. Its default value is 1.
94 |
95 | * *`callback`* <[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function)> Called with result when the stringify operation completes.
96 | * *`error`* <[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)>
97 | * *`result`* <[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type)> JSON value.
98 | ```sh
99 | const yj = require('yieldable-json')
100 | yj.stringifyAsync({key:"value"}, (err, data) => {
101 | if (!err)
102 | console.log(data)
103 | })
104 | ```
105 |
106 | Warning: While stringifyAsync is in progress (i.e. before the callback is executed), it is the user's responsibility to ensure that the Javascript object value (or any of its child objects) is not modified in any way. Object modification between the async function invocation and issuing of the completion callback may lead to undefined behavior (and can possibly result in an inadvertent crash or object corruption).
107 |
108 | ***parseAsync***:
109 |
110 | parseAsync(text[, reviver][, intensity], callback)
111 |
112 | * *`text`* <[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type)> The JSON string which needs to be parsed.
113 |
114 | * *`reviver`* `Optional` <[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function)> A function if specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the `reviver`. Then it is called, with the object containing the property being processed as this, and with the property name as a string, and the property value as arguments. If the `reviver` function returns undefined (or returns no value, for example, if execution falls off the end of the function), the property is deleted from the object. Otherwise, the property is redefined to be the return value.
115 | If the `reviver` only transforms some values and not others, be certain to return all untransformed values as-is, otherwise they will be deleted from the resulting object.
116 |
117 | * *`intensity`* `Optional` <[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type)> Takes the value between the range 1-32. This is used to compute the extent of traversal in the JSON string in one iteration. The more the intensity, the more time will be spent in the parse operation before it yields back to the uv loop. Its default value is 1.
118 |
119 | * *`callback`* <[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function)> Called with result when the parsing operation completes.
120 |
121 | * *`error`* <[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)>
122 | * *`result`* <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> JSON value.
123 | ```sh
124 | const yj = require('yieldable-json')
125 | yj.parseAsync('{"key":"value"}', (err, data) => {
126 | if (!err)
127 | console.log(data)
128 | })
129 | ```
130 |
131 | ### GitHub Issues
132 | This project uses GitHub Issues to track ongoing development and issues. Be sure
133 | to search for existing bugs before you create another one. Contributions are always welcome!
134 |
135 | - [https://github.com/ibmruntimes/yieldable-json/issues]( https://github.com/ibmruntimes/yieldable-json/issues)
136 |
137 | ### **Collaborators**
138 |
139 | * [bidipyne](https://github.com/bidipyne) -
140 | **Bidisha Pyne** <bidisha.pyne2015a@vit.ac.in> (she/her)
141 | * [gireeshpunathil](https://github.com/gireeshpunathil) -
142 | **Gireesh Punathil** <gpunathi@in.ibm.com> (he/him)
143 | * [HarshithaKP](https://github.com/HarshithaKP) -
144 | **Harshitha K P** <harshi46@in.ibm.com> (she/her)
145 | * [LakshmiSwethaG](https://github.com/LakshmiSwethaG) -
146 | **Lakshmi Swetha Gopireddy** <lakshmiswethagopireddy@gmail.com> (she/her)
147 | * [mhdawson](https://github.com/mhdawson) -
148 | **Michael Dawson** <michael_dawson@ca.ibm.com> (he/him)
149 | * [sam-github](https://github.com/sam-github) -
150 | **Sam Roberts** <vieuxtech@gmail.com> (he/him)
151 | * [sreepurnajasti](https://github.com/sreepurnajasti) -
152 | **Sreepurna Jasti** <sreepurna.jasti@gmail.com> (she/her)
153 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* **************************************************************************
2 | *
3 | * (c) Copyright IBM Corp. 2017
4 | *
5 | * This program and the accompanying materials are made available
6 | * under the terms of the Apache License v2.0 which accompanies
7 | * this distribution.
8 | *
9 | * The Apache License v2.0 is available at
10 | * http://www.opensource.org/licenses/apache2.0.php
11 | *
12 | * Contributors:
13 | * Multiple authors (IBM Corp.) - initial implementation and documentation
14 | ***************************************************************************/
15 | 'use strict';
16 |
17 | const pa = require('./yieldable-parser');
18 | const ps = require('./yieldable-stringify');
19 |
20 | /**
21 | * Checks whether the provided space
22 | * @param { string or number } space
23 | * @return { string or number }
24 | */
25 | let validateSpace = (space) => {
26 | if (typeof space === 'number') {
27 | space = Math.round(space);
28 | if (space >= 1 && space <= 10)
29 | return space;
30 | else if (space < 1)
31 | return 0;
32 | else
33 | return 10;
34 | } else {
35 | if (space.length <= 10)
36 | return space;
37 | else
38 | return space.substr(0, 9);
39 | }
40 | };
41 |
42 | /**
43 | * Checks whether the provided intensity
44 | * @param { number } intensity
45 | * @return { number }
46 | */
47 | let validateIntensity = (intensity) => {
48 | intensity = Math.round(intensity);
49 | if (intensity > 0 && intensity <= 32)
50 | return intensity;
51 | else if (intensity <= 0)
52 | return 1;
53 | else
54 | return 32;
55 | };
56 |
57 | module.exports = {
58 |
59 | /**
60 | * Error checking and call of appropriate functions for JSON parse
61 | * @param { primitive data types } data
62 | * @param { function or array } reviver
63 | * @param { number } intensity
64 | * @param { function } callback
65 | * @return { function } parseWrapper
66 | */
67 | parseAsync(data, reviver, intensity, callback) {
68 | const argv = arguments;
69 |
70 | //Bring parity with the in-built parser, that takes both string and buffer
71 | if(Buffer.isBuffer(data))
72 | data = data.toString();
73 |
74 | if (argv.length < 2)
75 | throw new Error('Missing Callback');
76 |
77 | if (typeof argv[argv.length - 1] === 'function') {
78 | callback = argv[argv.length - 1];
79 | reviver = null;
80 | intensity = 1;
81 | } else
82 | throw new TypeError('Callback is not a function');
83 |
84 | if (argv.length > 2) {
85 | let i = 1;
86 | if (typeof argv[i] === 'function')
87 | reviver = argv[i++];
88 | if (typeof argv[i] === 'number')
89 | intensity = validateIntensity(argv[i]);
90 | }
91 | return pa.parseWrapper(data, reviver, intensity, callback);
92 | },
93 |
94 | /**
95 | * Error checking and call of appropriate functions for JSON stringify API
96 | * @param { primitive data types } data
97 | * @param { function or array } replacer
98 | * @param { number or string } space
99 | * @param { number } intensity
100 | * @param { function } callback
101 | * @return { function } stringifyWrapper
102 | */
103 | stringifyAsync(data, replacer, space, intensity, callback) {
104 | const argv = arguments;
105 | if (typeof argv[argv.length - 1] === 'function') {
106 | callback = argv[argv.length - 1];
107 | replacer = null;
108 | intensity = 1;
109 | } else
110 | throw new TypeError('Callback is not a function');
111 | if (argv.length > 2) {
112 | let i = 1;
113 | if (typeof argv[i] === 'function' || typeof argv[i] === 'object')
114 | replacer = argv[i++];
115 | if ((typeof argv[i] === 'number' || typeof argv[i] === 'string') &&
116 | typeof argv[i++] === 'number')
117 | space = validateSpace(argv[i++]);
118 | if (typeof argv[i] === 'number')
119 | intensity = validateIntensity(argv[i]);
120 | }
121 | return ps.stringifyWrapper(data, replacer, space, intensity, callback);
122 | },
123 | };
124 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yieldable-json",
3 | "version": "2.0.1",
4 | "main": "index.js",
5 | "description": "An asynchronous yieldable version of JSON.stringify and JSON.parse",
6 | "author": "Gireesh Punathil ",
7 | "contributors": [
8 | "Bidisha Pyne ",
9 | "Lakshmi Swetha Gopireddy ",
10 | "Michael Dawson ",
11 | "Sam Roberts ",
12 | "Sreepurna Jasti "
13 | ],
14 | "keywords": [
15 | "stringify",
16 | "parse",
17 | "asynchronous",
18 | "async",
19 | "yield",
20 | "yieldable",
21 | "string",
22 | "object",
23 | "json",
24 | "non-blocking"
25 | ],
26 | "license": "Apache-2.0",
27 | "repository": "ibmruntimes/yieldable-json",
28 | "scripts": {
29 | "lint": "eslint .",
30 | "test": "tap test/*.js"
31 | },
32 | "engines": {
33 | "node": ">= 4.0.0"
34 | },
35 | "files": [
36 | "LICENSE",
37 | "CONTRIBUTING.md",
38 | "README.md",
39 | "index.js",
40 | "yieldable-stringify.js",
41 | "yieldable-parser.js",
42 | "test/",
43 | "utils/"
44 | ],
45 | "devDependencies": {
46 | "eslint": "^6.6.0",
47 | "eslint-config-strongloop": "^2.1.0",
48 | "tap": "^10.7.3",
49 | "seedrandom": "^2.4.3"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/test/test-index-of-generator.js:
--------------------------------------------------------------------------------
1 | const tap = require('tap');
2 | const { indexOfGenerator } = require('../utils/index-of-generator');
3 |
4 | function promisifyGenerator(generator){
5 | return (...args) => {
6 | const gen = generator(...args);
7 |
8 | function handleNext(resolvedValue){
9 | if(resolvedValue.done){
10 | return Promise.resolve(resolvedValue.value);
11 | }
12 | return Promise.resolve(gen.next(resolvedValue.value)).then(handleNext);
13 | }
14 |
15 |
16 | return Promise.resolve(gen.next())
17 | .then((resolvedValue) => handleNext(resolvedValue), (error) => Promise.reject(gen.throw(error)));
18 | }
19 | }
20 |
21 | const asyncIndexOf = promisifyGenerator(indexOfGenerator);
22 |
23 | const stringAsArray = ['s', 'o', 'm', 'e'];
24 | const text = stringAsArray.join('');
25 |
26 | tap.test('should return -1 if not found due to initialIndex', async () => {
27 | const index = await asyncIndexOf(text, 's', 1);
28 | const syncIndexOf = text.indexOf('s', 1);
29 | tap.equal(index, syncIndexOf);
30 | })
31 |
32 | tap.test('should return proper index', async () => {
33 | const index = await asyncIndexOf(text, 'e');
34 | const syncIndexOf = text.indexOf('e');
35 | tap.equal(index, syncIndexOf);
36 | })
37 |
38 | tap.test('should return proper test with initialIndex', async () => {
39 | const index = await asyncIndexOf(text, 'e', 2);
40 | const syncIndexOf = text.indexOf('e', 2);
41 | tap.equal(index, syncIndexOf);
42 | })
43 |
44 | tap.test('should return correct indexOf if initialIndex is same', async () => {
45 | const index = await asyncIndexOf(text, 'e', 3);
46 | const syncIndexOf = text.indexOf('e', 3);
47 | tap.equal(index, syncIndexOf);
48 | })
49 |
50 | tap.test('should return -1 if not found', async () => {
51 | const index = await asyncIndexOf(text, 'x');
52 | const syncIndexOf = text.indexOf('x');
53 | tap.equal(index, syncIndexOf);
54 | })
55 |
--------------------------------------------------------------------------------
/test/test-parse-async.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const str = '{"name":"Ila Gould","age":22,"gender":"female"}';
7 | let flag = false;
8 |
9 | yj.parseAsync(str, (err, obj) => {
10 | if (!err) {
11 | tap.ok(flag, 'Unexpected Async Behavior:' +
12 | ' callback called synchronously');
13 | } else
14 | tap.fail(err);
15 | });
16 |
17 | flag = true;
18 |
--------------------------------------------------------------------------------
/test/test-parse-basic.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const str = '{"name":"Ila Gould","age":22,"gender":"female","nested":"\\"value\\""}';
7 |
8 | // Make sure the API works well without the optional parameters.
9 | yj.parseAsync(str, (err, obj) => {
10 | if (!err)
11 | tap.equal(str, JSON.stringify(obj));
12 | else
13 | tap.fail(err);
14 | });
15 |
--------------------------------------------------------------------------------
/test/test-parse-big-escaped-string.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 | const { readFileSync } = require('fs');
6 | const str = readFileSync('./test/big-json-with-string.txt');
7 | const nodeJSNativeParse = JSON.parse(str);
8 |
9 | // Make sure the API works well without the optional parameters.
10 | yj.parseAsync(str, (err, obj) => {
11 | if (!err)
12 | tap.equal(obj.text, nodeJSNativeParse.text, 'Failed to parse big string message - omitting logs', { diagnostic: false });
13 | else
14 | tap.fail(err);
15 | });
16 |
--------------------------------------------------------------------------------
/test/test-parse-exponential.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const tap = require('tap');
4 | const yj = require('../index.js');
5 |
6 | yj.parseAsync('1e+2', (err, res) => {
7 | if (!err) {
8 | tap.equal('100', JSON.stringify(res));
9 | } else {
10 | tap.fail(err);
11 | }
12 | });
13 |
14 | yj.parseAsync('1e-2', (err, res) => {
15 | if (!err) {
16 | tap.equal('0.01', JSON.stringify(res));
17 | } else {
18 | tap.fail(err);
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/test/test-parse-intensity.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | var count = 0;
7 | var flag = 0;
8 | var i = 0;
9 | var arr = [];
10 | while (i < 2050) {
11 | arr.push(i++);
12 | }
13 | var str = JSON.stringify(arr);
14 |
15 | yj.parseAsync(str, 2, (err, obj) => {
16 | if (!err) {
17 | tap.ok(flag >= 1, 'Async function was expected to yield' +
18 | ` at least once, but got ${flag}!`);
19 | } else
20 | tap.fail(err);
21 | });
22 |
23 | function foo() {
24 | setTimeout(() => {
25 | count++;
26 | flag++;
27 | if (count === 1000)
28 | return;
29 | foo();
30 | }, 0);
31 | };
32 |
33 | foo();
34 |
--------------------------------------------------------------------------------
/test/test-parse-nestedquotes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | //string with nested quotes
7 | const str = '{"name":"Ila Gould","age":22,"gender":"female","nested":"\\"check\\""}';
8 |
9 | yj.parseAsync(str, (error, obj) => {
10 | if (!error){
11 | tap.equal(str,JSON.stringify(obj));
12 | yj.stringifyAsync(obj,(err,data) => {
13 | if(!err){
14 | tap.equal(data,str);
15 | }
16 | else
17 | tap.fail(err);
18 | })
19 | }
20 | else
21 | tap.fail(error);
22 | });
23 |
--------------------------------------------------------------------------------
/test/test-parse-params.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const str = '{"name":"Ila Gould","age":40,"gender":"female"}';
7 | var flag = false;
8 |
9 | var reviver = (key, value) => {
10 | // Change the value if type is number
11 | if (typeof value === 'number')
12 | return value * 2;
13 | else return value;
14 | };
15 |
16 | // Make sure all the parameters can co-exist and the API
17 | // can handle them gracefully.
18 | yj.parseAsync(str, reviver, 2, (err, obj) => {
19 | if (!err) {
20 | tap.ok(flag, 'Unexpected Async-Behavior:' +
21 | ' callback was called synchronously');
22 | tap.equal('{"name":"Ila Gould","age":80,"gender":"female"}',
23 | JSON.stringify(obj));
24 | } else
25 | tap.fail(err);
26 | });
27 |
28 | flag = true;
29 |
--------------------------------------------------------------------------------
/test/test-parse-reviver.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const str =
7 | '{"name":"Ila Gould","age":22,"gender":"female"}';
8 |
9 | var reviver = (key, value) => {
10 | // Change the value if type is number
11 | if (typeof value === 'number')
12 | return value * 2;
13 | return value;
14 | };
15 |
16 | // Make sure the API works just with reviver param
17 | yj.parseAsync(str, reviver, (err, obj) => {
18 | if (!err)
19 | tap.equal('{"name":"Ila Gould","age":44,"gender":"female"}',
20 | JSON.stringify(obj));
21 | else
22 | tap.fail(err);
23 | });
24 |
--------------------------------------------------------------------------------
/test/test-stringify-async.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const obj = {
7 | name: 'Jacqueline Poole',
8 | gender: 'female',
9 | };
10 |
11 | let flag = false;
12 |
13 | // Make sure the API is truely async and returns
14 | // earliest by the next process tick
15 | yj.stringifyAsync(obj, 0, 2, (err, str) => {
16 | if (!err){
17 | tap.ok(flag, 'Unexpected Async-Behavior: callback was called' +
18 | ' synchronously.');
19 | } else
20 | tap.fail(err);
21 | });
22 |
23 | flag = true;
24 |
--------------------------------------------------------------------------------
/test/test-stringify-basic.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const obj = {
7 | name: 'Jacqueline Poole',
8 | gender: 'female',
9 | age: 40,
10 | a: '"b"',
11 | };
12 |
13 | // Make sure the API works well without optional parameters
14 | yj.stringifyAsync(obj, (err, str) => {
15 | if (!err)
16 | tap.equal(
17 | '{"name":"Jacqueline Poole","gender":"female","age":40,"a":"\\"b\\""}',
18 | str
19 | );
20 | else
21 | tap.fail(err);
22 | });
23 |
--------------------------------------------------------------------------------
/test/test-stringify-intensity.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | var count = 0;
7 | var flag = 0;
8 | var i = 0;
9 | var obj = [];
10 | while (i < 2050) {
11 | obj.push(i++);
12 | }
13 |
14 | yj.stringifyAsync(obj, 0, 2, (err, str) => {
15 | if (!err) {
16 | tap.ok(flag >= 1, 'Async function was expected to yield at least' +
17 | ` once, but got ${flag}!`);
18 | } else
19 | tap.fail(err);
20 | });
21 |
22 | function foo() {
23 | setTimeout(() => {
24 | count++;
25 | flag++;
26 | if (count === 100)
27 | return;
28 | foo();
29 | }, 0);
30 | };
31 |
32 | foo();
33 |
--------------------------------------------------------------------------------
/test/test-stringify-nestedquotes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | //Object with nested quotes
7 | const obj = {
8 | name: 'Jacqueline Poole',
9 | gender: 'female',
10 | age: 40,
11 | a:"\"b\""
12 | };
13 |
14 | yj.stringifyAsync(obj, (err, str) => {
15 | if (!err){
16 | tap.equal(JSON.stringify(obj), str);
17 | yj.parseAsync(str,(error,data) => {
18 | if(!error){
19 | tap.deepEquals(data,obj);
20 | }
21 | else
22 | tap.fail(error);
23 | })
24 | }
25 | else
26 | tap.fail(err);
27 | });
28 |
--------------------------------------------------------------------------------
/test/test-stringify-params.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const obj = {
7 | name: 'Jacqueline Poole',
8 | gender: 'female',
9 | age: 40,
10 | };
11 |
12 | let flag = false;
13 |
14 | // Replacer function which just returns non-string type values
15 | let replacer = (key, value) => {
16 | if (typeof value === 'string')
17 | return undefined;
18 | return value;
19 | };
20 |
21 | // Make sure all the parameters can co-exist
22 | // and the API can handle them gracefully.
23 | yj.stringifyAsync(obj, replacer, 2, 2, (err, str) => {
24 | if (!err) {
25 | tap.ok(flag, 'Unexpected Async-Behavior:' +
26 | ' callback was called synchronously');
27 | tap.equal('{\n "age": 40\n}', str);
28 | } else
29 | tap.fail(err);
30 | });
31 |
32 | flag = true;
33 |
--------------------------------------------------------------------------------
/test/test-stringify-replacer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const objData = {
7 | name: 'Jacqueline Poole',
8 | gender: 'female',
9 | age: 40,
10 | };
11 |
12 | // Replacer function which just returns non-string type values
13 | let replacer = (key, value) => {
14 | if (typeof value === 'string')
15 | return undefined;
16 | return value;
17 | };
18 |
19 | // Make sure the API is working just with replacer function.
20 | yj.stringifyAsync(objData, replacer, (err, strData) => {
21 | if (!err)
22 | tap.equal('{"age":40}', strData);
23 | else
24 | tap.fail(err);
25 | });
26 |
--------------------------------------------------------------------------------
/test/test-stringify-same-object-twice.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const obj = {
7 | name: 'Jacqueline Poole',
8 | gender: 'female',
9 | age: 40,
10 | };
11 |
12 | const master = {arr: [ { a: obj }, { b: obj} ] };
13 |
14 | // Make sure presence of obj twice in the master
15 | // object does not cause revisit issues while
16 | // stringifying it - such as circular dependency
17 |
18 | yj.stringifyAsync(obj, (err, str) => {
19 | if (!err) {
20 | tap.ok(true, 'Repeated object presence cause no issues');
21 | } else
22 | tap.fail(err);
23 | });
24 |
--------------------------------------------------------------------------------
/test/test-stringify-space.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const yj = require('../index');
4 | const tap = require('tap');
5 |
6 | const obj = {
7 | name: 'Jacqueline Poole',
8 | gender: 'female',
9 | age: 40,
10 | };
11 |
12 | // Make sure the API is working just with space parameter
13 | yj.stringifyAsync(obj, null, '\n', (err, str) => {
14 | if (!err)
15 | tap.equal('{\n\n"name": "Jacqueline Poole",\n\n"gender":' +
16 | ' "female",\n\n"age": 40\n}', str);
17 | else
18 | tap.fail(err);
19 | });
20 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | /* **************************************************************************
2 | *
3 | * (c) Copyright IBM Corp. 2017
4 | *
5 | * This program and the accompanying materials are made available
6 | * under the terms of the Apache License v2.0 which accompanies
7 | * this distribution.
8 | *
9 | * The Apache License v2.0 is available at
10 | * http://www.opensource.org/licenses/apache2.0.php
11 | *
12 | * Contributors:
13 | * Multiple authors (IBM Corp.) - initial implementation and documentation
14 | ***************************************************************************/
15 | 'use strict';
16 |
17 | const jsony = require('../index');
18 | const tap = require('tap');
19 | const fs = require('fs');
20 | require('seedrandom');
21 | const util = require('util');
22 |
23 | const ARRAYMAX = 100;
24 | const STRMAX = 10;
25 | const OBJDEPTH = 32;
26 | const PREDICATE = 100;
27 | let seed = 0;
28 |
29 | if (typeof process.argv[2] !== 'undefined')
30 | seed = process.argv[2];
31 | else
32 | seed = Math.round(Math.random() * 100);
33 |
34 | Math.seedrandom(seed);
35 |
36 | let generateString = () => {
37 |
38 | let text = '';
39 | let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
40 | // let escapee = ['\'', '\\', '/', '\b', '\f', '\n', '\r', '\t']
41 | let special = ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
42 | '-', '_', '+', '='];
43 | let strlen = Math.round(Math.random() * STRMAX);
44 | for (let i = 0; i < strlen; i++) {
45 | text += possible.charAt(Math.round(Math.random() * possible.length));
46 |
47 | // TODO
48 | /*
49 | if (Math.round(Math.random() * PREDICATE) % escapee.length) {
50 | text += escapee[Math.round(Math.random() * escapee.length)]
51 | i++
52 | }
53 | */
54 | if (Math.round(Math.random() * PREDICATE) % 2) {
55 | text += special[Math.round(Math.random() * special.length)];
56 | i++;
57 | }
58 | // TODO
59 | /*
60 | if (Math.round(Math.random() * PREDICATE) % 2) {
61 | let uni = Math.round(Math.random() * 30)
62 | text += String.fromCharCode(parseInt(uni))
63 | i++
64 | }
65 | if (Math.round(Math.random() * PREDICATE) % 2) {
66 | let uni = Math.round(Math.floor() * 33) + 127
67 | text += String.fromCharCode(parseInt(uni))
68 | i++
69 | }
70 | if (Math.round(Math.random() * PREDICATE) % 2) {
71 | let uni = Math.round(Math.floor() * 5) + 1536
72 | text += String.fromCharCode(parseInt(uni))
73 | i++
74 | }
75 | */
76 | }
77 | return text;
78 | };
79 |
80 | let generateBool = () => {
81 | return !!Math.round(Math.random());
82 | };
83 |
84 | let generateObject = (depth) => {
85 | let obj = {};
86 | if (depth < 0)
87 | return null;
88 | let count = Math.round(Math.random() * ARRAYMAX);
89 | for (let i = 0; i < count; i++)
90 | obj[generateString()] = generateJson(--depth, 0);
91 | return obj;
92 | };
93 |
94 | let generateArray = (depth) => {
95 | let array = [];
96 | let element = null;
97 | if (depth < 0)
98 | element = null;
99 | let count = Math.round(Math.random() * ARRAYMAX);
100 | for (let i = 0; i < count; i++) {
101 | element = generateObject(--depth);
102 | if (element != null)
103 | array.push(element);
104 | }
105 | return array;
106 | };
107 |
108 | let generateJson = (depth, root) => {
109 | let num = Math.round(Math.random() * PREDICATE) % 5 + root;
110 | switch (num) {
111 | case 0:
112 | return generateString();
113 | case 1:
114 | return Math.round(Math.random() * 1.84467440737096E19);
115 | case 2:
116 | return generateBool();
117 | case 3:
118 | return null;
119 | case 4:
120 | case 5:
121 | return new Date(Math.random() * Date.now());
122 | case 6:
123 | case 8:
124 | case 9:
125 | return generateArray(--depth);
126 | case 7:
127 | case 10:
128 | case 11:
129 | return generateObject(--depth);
130 | default:
131 | return null;
132 | }
133 | };
134 |
135 | let print = (seed) => {
136 | console.log('Raise issue with https://github.com/ibmruntimes/yieldable-' +
137 | 'json issues, along with these files.');
138 | console.log('Alternatively, you can diagnose this issue with the help of' +
139 | 'these files.');
140 | console.log('Also use seeded number ' + seed + ' to debug futher.');
141 | console.log('Debug mode Usage: node test.js ');
142 | };
143 |
144 | let sourceObj = generateJson(OBJDEPTH, 6);
145 | let builtinStr = JSON.stringify(sourceObj);
146 | let fileFound = `errFound.${process.pid}.${Date.now()}.txt`;
147 | let fileWanted = `errWanted.${process.pid}.${Date.now()}.txt`;
148 | jsony.stringifyAsync(sourceObj, function(err, oldStr) {
149 | if (err) {
150 | console.log('StringifyAsync Error: Test Failed');
151 | console.log('Error in StringifyAsync, Redirected to file: ' + fileFound);
152 | fs.writeFileSync(fileFound, util.inspect(err), 'utf-8');
153 | print(seed);
154 | return;
155 | }
156 |
157 | if (oldStr === builtinStr) {
158 | tap.pass('Test Passed');
159 | } else {
160 | console.log('StringifyAsync: Test Failed');
161 | console.log('Expected String Redirected to file: ' + fileWanted);
162 | fs.writeFileSync(fileWanted, oldStr);
163 | console.log('Actual String Redirected to file: ' + fileFound);
164 | fs.writeFileSync(fileFound, builtinStr);
165 | print(seed);
166 | return;
167 | }
168 |
169 | jsony.parseAsync(oldStr, function(errp, newObj) {
170 | if (errp) {
171 | console.log('ParseError: Test Failed');
172 | console.log('Error in Parse, Redirected to file: ' + fileFound);
173 | fs.writeFileSync(fileFound, util.inspect(errp), 'utf-8');
174 | print(seed);
175 | return;
176 | } else {
177 | jsony.stringifyAsync(newObj, function(errs, newStr) {
178 | if (errs) {
179 | console.log('StringifyAsync Error: Test Failed');
180 | console.log('Error in StringifyAsync, Redirected to file: ' +
181 | fileFound);
182 | fs.writeFileSync(fileFound, util.inspect(errs), 'utf-8');
183 | print(seed);
184 | return;
185 | }
186 |
187 | if (newStr === builtinStr) {
188 | tap.pass('Test Passed');
189 | } else {
190 | console.log('ParseAsync: Test Failed');
191 | console.log('Expected String Redirected to file: ' + fileWanted);
192 | fs.writeFileSync(fileWanted, newStr);
193 | console.log('Actual String Redirected to file: ' + fileFound);
194 | fs.writeFileSync(fileFound, builtinStr);
195 | print(seed);
196 | }
197 | });
198 | }
199 | });
200 | });
201 |
--------------------------------------------------------------------------------
/test/test_nested_parse.js:
--------------------------------------------------------------------------------
1 | const yj = require('../index.js');
2 | const tap = require('tap');
3 | const obj = '{"obj":{"child":{}}}'
4 | yj.parseAsync(obj,(err,res) => {
5 | if(!err) {
6 | tap.equal(obj, JSON.stringify(res));
7 | }
8 | else {
9 | tap.fail(err);
10 | }
11 | yj.parseAsync(obj,(err,res) => {
12 | if(!err) {
13 | tap.equal(obj, JSON.stringify(res));
14 | }
15 | else {
16 | tap.fail(err);
17 | }
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/test/test_nested_stringify.js:
--------------------------------------------------------------------------------
1 | const yj = require('../index.js')
2 | const tap = require('tap')
3 |
4 | const child = {}
5 | const obj = {child}
6 |
7 | yj.stringifyAsync({obj}, (err, res) => {
8 | if(!err) {
9 | tap.equal(JSON.stringify({obj}), res)
10 | }
11 | else {
12 | tap.fail(err)
13 | }
14 | yj.stringifyAsync({obj}, (err, res) => {
15 | if(!err) {
16 | tap.equal(JSON.stringify({obj}), res)
17 | }
18 | else {
19 | tap.fail(err)
20 | }
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/utils/index-of-generator.js:
--------------------------------------------------------------------------------
1 | //write more performant option for indexOf
2 | //if more than 50k characters, use BHM Algorithm
3 | //consider a big string to be 10k characters
4 | const MAX_STRING_SIZE = 10000;
5 |
6 | module.exports.indexOfGenerator = function* indexOfGenerator(stringInput, searchInput, initialIndex = 0) {
7 | if (!stringInput || !stringInput.length) {
8 | return -1;
9 | }
10 | stringInput = stringInput.slice(initialIndex);
11 | let innerAt = 0;
12 | let index = -1;
13 | while (true) {
14 | const at = Math.max(innerAt - 1, 0);
15 | yield chunk = stringInput.slice(at, innerAt + MAX_STRING_SIZE);
16 | if (!chunk.length) {
17 | return index;
18 | }
19 | const indexOf = chunk.indexOf(searchInput);
20 | if (indexOf !== -1) {
21 | yield index = indexOf + at + initialIndex;
22 | return index;
23 | }
24 | innerAt += chunk.length + 1;
25 | }
26 | }
--------------------------------------------------------------------------------
/yieldable-parser.js:
--------------------------------------------------------------------------------
1 | /* **************************************************************************
2 | *
3 | * (c) Copyright IBM Corp. 2017
4 | *
5 | * This program and the accompanying materials are made available
6 | * under the terms of the Apache License v2.0 which accompanies
7 | * this distribution.
8 | *
9 | * The Apache License v2.0 is available at
10 | * http://www.opensource.org/licenses/apache2.0.php
11 | *
12 | * Contributors:
13 | * Multiple authors (IBM Corp.) - initial implementation and documentation
14 | ***************************************************************************/
15 | 'use strict';
16 | const { indexOfGenerator } = require('./utils/index-of-generator');
17 |
18 | /**
19 | * This method parses a JSON text to produce an object or array.
20 | * It can throw a SyntaxError exception, if the string is malformed.
21 | * @param { string } text
22 | * @param { function or array } reviver
23 | * @param { number } intensity
24 | * @param { function } cb
25 | * @return { function } yieldCPU
26 | */
27 | let parseWrapper = (text, reviver, intensity, cb) => {
28 | let counter = 0;
29 | let keyN = 0;
30 | let parseStr = text;
31 | let at = 0;
32 | let ch = ' ';
33 | let word = '';
34 | function ParseError(m) {
35 | this.name = 'ParseError';
36 | this.message = m;
37 | this.text = parseStr;
38 | }
39 |
40 | // Seek to the next character, after skipping white spaces, if any.
41 | let seek = () => {
42 | ch = parseStr.charAt(at);
43 | at++;
44 | while (ch && ch <= ' ') {
45 | seek();
46 | }
47 | return ch;
48 | };
49 |
50 | // Seek to the previous character, required in some special cases.
51 | let unseek = () => {
52 | ch = parseStr.charAt(--at);
53 | };
54 |
55 | // Match 'true', 'false' and 'null' built-ins.
56 | let wordCheck = () => {
57 | word = '';
58 | do {
59 | word += ch;
60 | seek();
61 | } while (ch.match(/[a-z]/i));
62 | parseStr = parseStr.slice(at - 1);
63 | at = 0;
64 | return word;
65 | };
66 |
67 | // Process strings specially.
68 | let normalizeUnicodedString = function*() {
69 | let inQuotes = ' ';
70 | let tempIndex = at;
71 | let index = 0;
72 | let slash = 0;
73 | let c = '"';
74 | while (c) {
75 | index = yield * indexOfGenerator(parseStr, '"', tempIndex + 1);
76 | tempIndex = index;
77 | ch = parseStr.charAt(tempIndex - 1);
78 | while (ch === '\\') {
79 | slash++;
80 | ch = parseStr.charAt(tempIndex - (slash + 1));
81 | }
82 | if (slash % 2 === 0) {
83 | inQuotes = parseStr.substring(at, index);
84 | parseStr = parseStr.slice(++index);
85 | slash = 0;
86 | break;
87 | } else
88 | slash = 0;
89 | }
90 |
91 | // When parsing string values, look for " and \ characters.
92 | index = yield * indexOfGenerator(inQuotes, '\\');
93 | while (index >= 0) {
94 | let escapee = {
95 | '"': '"',
96 | '\'': '\'',
97 | '/': '/',
98 | '\\': '\\',
99 | b: '\b',
100 | f: '\f',
101 | n: '\n',
102 | r: '\r',
103 | t: '\t',
104 | };
105 | let hex = 0;
106 | let i = 0;
107 | let uffff = 0;
108 | at = index;
109 | ch = inQuotes.charAt(++at);
110 | if (ch === 'u') {
111 | uffff = 0;
112 | for (i = 0; i < 4; i += 1) {
113 | hex = parseInt(ch = inQuotes.charAt(++at), 16);
114 | if (!isFinite(hex)) {
115 | break;
116 | }
117 | uffff = uffff * 16 + hex;
118 | }
119 | inQuotes = inQuotes.slice(0, index) +
120 | String.fromCharCode(uffff) + inQuotes.slice(index + 6);
121 | at = index;
122 | } else if (typeof escapee[ch] === 'string') {
123 | inQuotes = inQuotes.slice(0, index) +
124 | escapee[ch] + inQuotes.slice(index + 2);
125 | at = index + 1;
126 | } else
127 | break;
128 | index = yield * indexOfGenerator(inQuotes, '\\', at);
129 | }
130 | at = 0;
131 | return inQuotes;
132 | };
133 |
134 | /**
135 | * This function parses the current string and returns the JavaScript
136 | * Object, through recursive method, and yielding back occasionally
137 | * based on the intensity parameter.
138 | * @return { object } returnObj
139 | */
140 | function * parseYield() {
141 | let key = '';
142 | let returnObj = {};
143 | let returnArr = [];
144 | let v = '';
145 | let inQuotes = '';
146 | let num = 0;
147 | let numHolder = '';
148 | let addup = () => {
149 | numHolder += ch;
150 | seek();
151 | };
152 | // Handle premitive types. eg: JSON.parse(21)
153 | if (typeof parseStr === 'number' || typeof parseStr === 'boolean' ||
154 | parseStr === null) {
155 | parseStr = '';
156 | return text;
157 | } else if (typeof parseStr === 'undefined') {
158 | parseStr = undefined;
159 | return text;
160 | } else if (parseStr.charAt(0) === '[' && parseStr.charAt(1) === ']') {
161 | parseStr = '';
162 | return [];
163 | } else if (parseStr.charAt(0) === '{' && parseStr.charAt(1) === '}') {
164 | parseStr = '';
165 | return {};
166 | } else {
167 | // Yield the parsing work at specified intervals.
168 | if (++counter > 512 * intensity) {
169 | counter = 0;
170 | yield;
171 | }
172 | // Common case: non-premitive types.
173 | if (keyN !== 1)
174 | seek();
175 | switch (ch) {
176 | case '{':
177 | // Object case
178 | seek();
179 | if (ch === '}') {
180 | parseStr = parseStr.slice(at);
181 | at = 0;
182 | return returnObj;
183 | }
184 | do {
185 | if (ch !== '"')
186 | seek();
187 | keyN = 1;
188 | key = yield *parseYield();
189 | keyN = 0;
190 | seek();
191 | returnObj[key] = yield *parseYield();
192 | seek();
193 | if (ch === '}') {
194 | parseStr = parseStr.slice(at);
195 | at = 0;
196 | return returnObj;
197 | }
198 | } while (ch === ',');
199 | return new ParseError('Bad object');
200 | case '[':
201 | // Array case
202 | seek();
203 | if (ch === ']') {
204 | parseStr = parseStr.slice(at);
205 | at = 0;
206 | return returnArr;
207 | }
208 | unseek();
209 | do {
210 | v = yield *parseYield();
211 | returnArr.push(v);
212 | seek();
213 | if (ch === ']') {
214 | parseStr = parseStr.slice(at);
215 | at = 0;
216 | return returnArr;
217 | }
218 | } while (ch === ',');
219 | return new ParseError('Bad array');
220 | case '"':
221 | parseStr = parseStr.slice(at - 1);
222 | at = 0;
223 | if (parseStr.charAt(0) === '"' && parseStr.charAt(1) === '"') {
224 | parseStr = parseStr.slice(2);
225 | at = 0;
226 | return inQuotes;
227 | } else {
228 | seek();
229 | return yield * normalizeUnicodedString();
230 | }
231 | case '0':
232 | case '1':
233 | case '2':
234 | case '3':
235 | case '4':
236 | case '5':
237 | case '6':
238 | case '7':
239 | case '8':
240 | case '9':
241 | case '-':
242 | if (ch === '-') addup();
243 | do {
244 | addup();
245 | if (ch === '.' || ch === 'e' || ch === 'E' ||
246 | ch === '-' || ch === '+' ||
247 | (ch >= String.fromCharCode(65) &&
248 | ch <= String.fromCharCode(70)))
249 | addup();
250 | } while (ch === '-' || ch === '+' || (isFinite(ch) && ch !== ''));
251 | num = Number(numHolder);
252 | parseStr = parseStr.slice(at - 1);
253 | at = 0;
254 | return num;
255 | case 't':
256 | word = wordCheck();
257 | if (word === 'true')
258 | return true;
259 | else return new ParseError('Unexpected character');
260 | case 'f':
261 | word = wordCheck();
262 | if (word === 'false')
263 | return false;
264 | else return new ParseError('Unexpected character');
265 | case 'n':
266 | word = wordCheck();
267 | if (word === 'null')
268 | return null;
269 | else return new ParseError('Unexpected character');
270 | default:
271 | return new ParseError('Unexpected character');
272 | }
273 | }
274 | }
275 |
276 | /**
277 | * If there is a reviver function, we recursively walk the new structure,
278 | * passing each name/value pair to the reviver function for possible
279 | * transformation, starting with a temporary root object that holds the result
280 | * in an empty key. If there is not a reviver function, we simply return the
281 | * result.
282 | * @param { object } yieldedObject
283 | * @param { string } key
284 | * @return { function } reviver
285 | */
286 | let revive = (yieldedObject, key) => {
287 | let k = '';
288 | let v = '';
289 | let val = yieldedObject[key];
290 | if (val && typeof val === 'object') {
291 | for (k in val) {
292 | if (Object.prototype.hasOwnProperty.call(val, k)) {
293 | v = revive(val, k);
294 | if (v !== undefined)
295 | val[k] = v;
296 | else
297 | delete val[k];
298 | }
299 | }
300 | }
301 | return reviver.call(yieldedObject, key, val);
302 | };
303 |
304 | let yielding = '';
305 | // To hold 'parseYield' genarator function
306 | function * yieldBridge() {
307 | yielding = yield* parseYield();
308 | }
309 | let rs = yieldBridge();
310 | let gen = rs.next();
311 |
312 | // Main yield control logic.
313 | let yieldCPU = () => {
314 | setImmediate(() => {
315 | gen = rs.next();
316 |
317 | if (gen && gen.done === true) {
318 | let isEmpty = (value) => {
319 | if (value.charAt(0) === '}' || value.charAt(0) === ']')
320 | value = value.substring(1, value.length);
321 | return typeof value === 'string' && !value.trim();
322 | };
323 | if (typeof yielding === 'undefined')
324 | return cb(new ParseError('Unexpected Character'), null);
325 | else if (yielding instanceof ParseError)
326 | return cb(yielding, null);
327 | else if (!isEmpty(parseStr))
328 | return cb(new ParseError('Unexpected Character'), null);
329 | else {
330 | if (reviver !== null) {
331 | if (typeof reviver === 'function') {
332 | let result = revive({'': yielding}, '');
333 | return cb(null, result);
334 | }
335 | } else
336 | return cb(null, yielding);
337 | }
338 | }
339 | yieldCPU();
340 | });
341 | };
342 | return yieldCPU();
343 | };
344 |
345 | exports.parseWrapper = parseWrapper;
346 |
--------------------------------------------------------------------------------
/yieldable-stringify.js:
--------------------------------------------------------------------------------
1 | /* **************************************************************************
2 | *
3 | * (c) Copyright IBM Corp. 2017
4 | *
5 | * This program and the accompanying materials are made available
6 | * under the terms of the Apache License v2.0 which accompanies
7 | * this distribution.
8 | *
9 | * The Apache License v2.0 is available at
10 | * http://www.opensource.org/licenses/apache2.0.php
11 | *
12 | * Contributors:
13 | * Multiple authors (IBM Corp.) - initial implementation and documentation
14 | ***************************************************************************/
15 | 'use strict';
16 |
17 | let counter = 0;
18 | let objStack = [];
19 | let temp = '';
20 | const limit = 100000;
21 |
22 | function StringifyError(m) {
23 | this.name = 'Error';
24 | this.message = m;
25 | }
26 |
27 | /**
28 | * Checking for unicode and backslash characters and replaces if any.
29 | * @param { string }
30 | * @return { string }
31 | */
32 |
33 | let normalize = (string, flagN) => {
34 | let retStr = '';
35 | let transform = '';
36 | let uc =
37 | '/[\\\'\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4' +
38 | '\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g';
39 | let unicode = new RegExp(uc);
40 | // Taking '\\' out of the loop to avoid change in
41 | // order of execution of object entries resulting
42 | // in unwanted side effect
43 | string = string.replace(/\\/gi, '\\\\');
44 | let escape = {
45 | '\b': '\\b',
46 | '\t': '\\t',
47 | '\n': '\\n',
48 | '\f': '\\f',
49 | '\r': '\\r',
50 | '"': '\\"',
51 | };
52 | // Escape is implemented globally
53 | for(var pattern in escape) {
54 | var regex = new RegExp(pattern,'gi')
55 | string = string.replace(regex, escape[pattern])
56 | }
57 | unicode.lastIndex = 0;
58 | if (unicode.test(string)) {
59 | // Unicode logic here
60 | transform = string.replace(unicode, (a) => {
61 | return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
62 | });
63 | if (flagN === 1) {
64 | transform += temp;
65 | transform += transform;
66 | temp = '';
67 | return '"' + transform + '"';
68 | } else if (flagN === 2) {
69 | return '"' + transform + '"';
70 | } else {
71 | temp += transform;
72 | }
73 | } else {
74 | if (flagN === 1) {
75 | retStr += temp;
76 | retStr += string;
77 | temp = '';
78 | return '"' + retStr + '"';
79 | } else if (flagN === 2) {
80 | return '"' + string + '"';
81 | } else {
82 | temp += string;
83 | return;
84 | }
85 | }
86 | };
87 |
88 | /**
89 | * Obtain stringified value by yielding at required intensity
90 | * @param { string} field
91 | * @param { primitive data type } container
92 | * @param { function or array } replacer
93 | * @param { number or string } space
94 | * @param { number } intensity
95 | * @return { function } yieldCPU
96 | */
97 |
98 | function * stringifyYield(field, container, replacer, space, intensity) {
99 | let itr = 0;
100 | let key = '';
101 | let val = '';
102 | let length = 0;
103 | let tempVal = '';
104 | let result = '';
105 | let value = container[field];
106 | // Made scope local handling async issues
107 | let flag1 = 0;
108 | let returnStr = '';
109 | let subStr = '';
110 | let len = 0;
111 |
112 | // Yield the stringification at definite intervals
113 | if (++counter > 512 * intensity) {
114 | counter = 0;
115 | yield val;
116 | }
117 |
118 | // Call replacer if one is present (SPEC)
119 | if (typeof replacer === 'function') {
120 | value = replacer.call(container, field, value);
121 | }
122 |
123 | switch (typeof value) {
124 | case 'string':
125 | if (value.length > limit) {
126 | for (let l = 0; l < value.length; l += limit) {
127 | flag1 = 0;
128 | yield value;
129 | subStr = value.substr(l, limit);
130 | len += subStr.length;
131 | if (len === value.length)
132 | flag1 = 1;
133 | returnStr = normalize(subStr, flag1);
134 | }
135 | } else
136 | returnStr = normalize(value, 2);
137 | return returnStr;
138 | case 'number':
139 | return isFinite(value)
140 | ? String(value)
141 | : 'null';
142 | case 'boolean':
143 | case 'null':
144 | return String(value);
145 | case 'undefined':
146 | return;
147 | case 'object':
148 | if (!value)
149 | return 'null';
150 |
151 | // Manage special cases of Arrays and Objects
152 | let getResult = (decision) => {
153 | if (result.length === 0)
154 | if (decision)
155 | return '{}';
156 | else
157 | return '[]';
158 | else
159 | if (decision)
160 | if (space)
161 | return '{\n' + space + result.join(',\n' + space) + '\n' + '}';
162 | else
163 | return '{' + result.join(',') + '}';
164 | else
165 | if (space)
166 | return '[\n' + space + result.join(',\n' + space) + '\n' + ']';
167 | else
168 | return '[' + result.join(',') + ']';
169 | };
170 |
171 | result = [];
172 | // If toJSON is present, invoke it (SPEC)
173 | if (value && typeof value.toJSON === 'function') {
174 | result.push('"' + value.toJSON(field) + '"');
175 | if (result.length === 0)
176 | return '{}';
177 | else
178 | if (space)
179 | return space + result.join(',\n' + space) + '\n';
180 | else
181 | return result.join(',');
182 | }
183 | // Array case
184 | if (value && value.constructor === Array) {
185 | length = value.length;
186 | for (itr = 0; itr < length; itr += 1) {
187 | tempVal =
188 | yield *stringifyYield(itr, value, replacer, space, intensity) ||
189 | 'null';
190 | if (tempVal !== undefined)
191 | result.push(tempVal);
192 | }
193 | return getResult(false);
194 | }
195 |
196 | // Manage replacing object scenario (SPEC)
197 | if (replacer && typeof replacer === 'object') {
198 | length = replacer.length;
199 | for (itr = 0; itr < length; itr += 1) {
200 | if (typeof replacer[itr] === 'string') {
201 | key = replacer[itr];
202 | val = yield *stringifyYield(key, value, replacer, space, intensity);
203 | if (val !== undefined)
204 | result.push(normalize(key, 2) + (space
205 | ? ': '
206 | : ':') + val);
207 | }
208 | }
209 | } else {
210 | // Object case
211 | objStack.push(value);
212 | for (key in value) {
213 | if (typeof value[key] === 'object' && value[key] !== null &&
214 | value[key] !== undefined) {
215 | if (objStack.indexOf(value[key]) !== -1) {
216 | return new StringifyError('Circular Structure Detected');
217 | } else
218 | objStack.push(value[key]);
219 | }
220 | if (Object.hasOwnProperty.call(value, key)) {
221 | val = yield *stringifyYield(key, value, replacer, space, intensity);
222 | if (val !== undefined)
223 | result.push(normalize(key, 2) + (space
224 | ? ': '
225 | : ':') + val);
226 | }
227 | objStack = objStack.filter((v, i, a) => { return v !== value[key] });
228 | }
229 | objStack = objStack.filter((v, i, a) => { return v !== value });
230 | }
231 | return getResult(true);
232 | default:
233 | return new StringifyError('Unexpected Character');
234 | }
235 | }
236 |
237 | /**
238 | * Calling appropriate functions each time.
239 | * @param { primitive data types } value
240 | * @param { function or array } replacer
241 | * @param { number or string } space
242 | * @param { number } intensity
243 | * @param { function } callback
244 | * @return { function } yieldCPU
245 | */
246 |
247 | let stringifyWrapper = (value, replacer, space, intensity, callback) => {
248 | let indent = '';
249 | if (typeof space === 'number') {
250 | indent = ' '.repeat(space);
251 | } else if (typeof space === 'string') {
252 | indent = space;
253 | }
254 |
255 | let yielding;
256 |
257 | // To hold 'stringifyYield' genarator function
258 | function * yieldBridge() {
259 | yielding = yield *stringifyYield('', {'': value}, replacer, indent, 1);
260 | }
261 |
262 | let rs = yieldBridge();
263 | let g = rs.next();
264 |
265 | let yieldCPU = () => {
266 | setImmediate(() => {
267 | g = rs.next();
268 | if (g && g.done === true) {
269 | // Reinitializing the values at the end of API call
270 | counter = 0;
271 | temp = ''
272 | objStack = [];
273 | if (typeof yielding === 'object')
274 | return callback(yielding, null);
275 | else
276 | return callback(null, yielding);
277 | }
278 | yieldCPU();
279 | });
280 | };
281 | return yieldCPU();
282 | };
283 |
284 | exports.stringifyWrapper = stringifyWrapper;
285 |
--------------------------------------------------------------------------------