├── .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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
Data VolumeLoop starvation (in milliseconds)
JSON (built-in)yieldable-json
115 KB25
327 KB105
1.3 MB505
2.2 MB1005
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 | --------------------------------------------------------------------------------