├── .babelrc.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── build ├── es5.js └── package.js ├── lib ├── cli.js ├── import.mjs ├── index.js ├── json6.js └── require.js ├── package.json ├── package.json6 ├── rollup.config.js └── test ├── 1.0.9-require.js ├── 1.0.9-require.json6 ├── 1.0.9-require.mjs ├── 1.0.9-stringify.js ├── 1.0.9.js ├── 1.1.2-bad-require.js ├── 1.1.2-bad-require.json6 ├── TestObjectKeys.js ├── benchmarks ├── fundamentals │ ├── stringBench.js │ └── testNumberConvert.js ├── json6NumberTest.js ├── json6ObjectArrays.js ├── json6StringTest.js └── json6Test.js ├── escape.js ├── json6BadTest.js ├── json6StreamTest.js ├── json6Test.js ├── json6TestObject2.js ├── json6TestObjectArray.js ├── json6_internals_test.js ├── numberTest.js ├── stream.json6 ├── streamTest.js ├── stringEscapes.js ├── test-single-json6.js ├── testIncompleteStringEscapes.js ├── testJsonDecode.js └── testjson6.js /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "node": 10 6 | } 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig file: https://EditorConfig.org 2 | ; Install the "EditorConfig" plugin into your editor to use 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | indent_style = tab 13 | 14 | [{*.yml,*.md}] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [{*.json,*.json6}] 19 | indent_style = space 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tests 4 | !*.js 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | env: { 5 | browser: true, 6 | commonjs: true, 7 | es6: true, 8 | node: true 9 | }, 10 | extends: 'eslint:recommended', 11 | overrides: [{ 12 | files: 'test/**', 13 | globals: { 14 | expect: 'readonly' 15 | }, 16 | env: {mocha: true} 17 | },{ 18 | files: 'lib/import.mjs', 19 | parserOptions: { 20 | ecmaVersion : 2020, 21 | sourceType: 'module', 22 | } 23 | }], 24 | globals: { 25 | Atomics: 'readonly', 26 | SharedArrayBuffer: 'readonly' 27 | }, 28 | parserOptions: { 29 | ecmaVersion: 2018 30 | }, 31 | rules: { 32 | indent: ['error', 'tab'], 33 | strict: ['error'], 34 | semi: ['error'], 35 | 'prefer-const': ['error'], 36 | 'no-var': ['error'], 37 | 'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'] 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | package-lock.json 4 | .eslintcache 5 | node_modules 6 | .nyc_output 7 | .vscode 8 | *~ 9 | report.*.json 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | tests 3 | coverage 4 | .nyc_output 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | # - 10 4 | - 12 5 | - 14 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017+ d3x0r (github id) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ------ 24 | If you make notable improvments/fixes I would appreciate a best effort to share 25 | such changes; or at least share that there might be a possibility of improvement. 26 | There is no penalty if nothing is shared. I attempted to get JSON5 maintainers 27 | to implement the changes before re-implementing, but they were unresponsive. 28 | Besides this implementation is twice the speed, so it benefits a lot from not 29 | being based on their code in the first place. It's only based on the description 30 | of their extensions to original JSON format. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON6 – JSON for Humans 2 | 3 | [![Build Status](https://travis-ci.com/d3x0r/JSON6.svg?branch=master)](https://travis-ci.com/d3x0r/JSON6) 4 | 5 | [![Join the chat at https://gitter.im/sack-vfs/json6](https://badges.gitter.im/sack-vfs/json6.svg)](https://gitter.im/sack-vfs/json6?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | *Documentation base cloned from JSON5 project https://github.com/json5/json5* 8 | 9 | JSON is an excellent data format, but can be better, and more expressive. 10 | 11 | **JSON6 is a proposed extension to JSON** (Proposed here, noone, like em-discuss 12 | seemed to care about such a thing; prefering cryptic solutions like json-schema, 13 | or the 1000 pound gorilla solution). It aims to make it easier for 14 | *humans to write and maintain* by hand. It does this by adding some minimal 15 | syntax features directly from ECMAScript 6. 16 | 17 | JSON6 is a **superset of JavaScript**, although adds **no new data types**, 18 | and **works with all existing JSON content**. Some features allowed in JSON6 19 | are not directly supported by Javascript; although all javascript parsable 20 | features can be used in JSON6, except functions or any other code construct, 21 | transporting only data save as JSON. 22 | 23 | JSON6 is *not* an official successor to JSON, and JSON6 content may *not* 24 | work with existing JSON parsers. For this reason, JSON6 files use a new .json6 25 | extension. *(TODO: new MIME type needed too.)* 26 | 27 | The code is a **reference JavaScript implementation** for both Node.js 28 | and all browsers. It is a completly new implementation. 29 | 30 | Other related : [JSOX](https://github.com/d3x0r/jsox) JS Object Exchange format, which builds 31 | upon this and adds additional support for Date, BigNum, custom emissions, 32 | keyword-less class defintitions;default initializers, data condensation, 33 | flexible user exensibility. 34 | 35 | 36 | ## Why 37 | 38 | JSON isn’t the friendliest to *write*. Keys need to be quoted, objects and 39 | arrays can’t have trailing commas, and comments aren’t allowed — even though 40 | none of these are the case with regular JavaScript today. 41 | 42 | That was fine when JSON’s goal was to be a great data format, but JSON’s usage 43 | has expanded beyond *machines*. JSON is now used for writing [configs][ex1], 44 | [manifests][ex2], even [tests][ex3] — all by *humans*. 45 | 46 | [ex1]: http://plovr.com/docs.html 47 | [ex2]: https://www.npmjs.org/doc/files/package.json.html 48 | [ex3]: http://code.google.com/p/fuzztester/wiki/JSONFileFormat 49 | 50 | There are other formats that are human-friendlier, like YAML, but changing 51 | from JSON to a completely different format is undesirable in many cases. 52 | JSON6’s aim is to remain close to JSON and JavaScript. 53 | 54 | 55 | ## Features 56 | 57 | The following is the exact list of additions to JSON’s syntax introduced by 58 | JSON6. **All of these are optional**, and **MOST of these come from ES5/6**. 59 | 60 | ## Caveats 61 | 62 | Does not include stringify, instead falling back to original (internal) JSON.stringify. 63 | This will cause problems maintaining undefined, Infinity and NaN type values. 64 | 65 | ### Summary of Changes from JSON5 66 | 67 | JSON6 includes all features of JSON5 plus the following. 68 | 69 | - Keyword `undefined` 70 | - Objects/Strings back-tick quoted strings (no template support, just uses same quote); Object key names can be unquoted. 71 | - Strings - generous multiline string definition; all javascript character escapes work. \(\0, \x##, \u####, \u\{\} \) 72 | - Numbers - underscore digit separation in numbers, octal `0o` and binary `0b` formats; all javascript number notations. 73 | - Arrays - empty members 74 | - Streaming reader interface 75 | - (Twice the speed of JSON5; subjective) 76 | 77 | ### Objects 78 | 79 | - Object keys can be unquoted if they do not have ':', ']', '[', '{', '}', ',', any quote or whitespace; keywords will be interpreted as strings. 80 | 81 | - Object keys can be single-quoted, (**JSON6**) or back-tick quoted; any valid string 82 | 83 | - Object keys can be double-quoted (original JSON). 84 | 85 | - Objects can have a single trailing comma. Excessive commas in objects will cause an exception. '{ a:123,,b:456 }' is invalid. 86 | 87 | [mdn_variables]: https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables 88 | 89 | ### Arrays 90 | 91 | - Arrays can have trailing commas. If more than 1 is found, additional empty elements will be added. 92 | 93 | - (**JSON6**) Arrays can have comma ( ['test',,,'one'] ), which will result with empty values in the empty places. 94 | 95 | ### Strings 96 | 97 | - Strings can be double-quoted (as per original JSON). 98 | 99 | - Strings can be single-quoted. 100 | 101 | - Strings can be back-tick (\`) ([grave accent](https://en.wikipedia.org/wiki/Grave_accent)) -quoted. 102 | 103 | - Strings can be split across multiple lines; just prefix each newline with a 104 | backslash. [ES5 [§7.8.4](http://es5.github.com/#x7.8.4)] 105 | 106 | - (**JSON6**) all strings will continue keeping every character between the start and end, this allows multi-line strings 107 | and keep the newlines in the string; if you do not want the newlines they can be escaped as previously mentioned. 108 | 109 | - (**JSON5+?**) Strings can have characters emitted using 1 byte hex, interpreted as a utf8 codepoint `\xNN`, 2 and only 2 hex digits must follow `\x`; they may be 4 byte unicode characters `\uUUUU`, 4 and only 4 hex digits must follow `\u`; higher codepoints can be specified with `\u{HHHHH}`, (where H is a hex digit) This is permissive and may accept a single hex digit between `{` and `}`. All other standard escape sequeneces are also recognized. Any character that is not recognized as a valid escape character is emitted without the leading escape slash ( for example, `"\012"` will parse as `"012"` 110 | 111 | - (**JSON6**) The interpretation of newline is dynamic treating `\r`, `\n`, and `\r\n` as valid combinations of line ending whitespace. The `\` will behave approrpriately on those combinations. Mixed line endings like `\n\r?` or `\n\r\n?` are two line endings; 1 for newline, 1 for the \r(follwed by any character), and 1 for the newline, and 1 for the \r\n pair in the second case. 112 | 113 | ### Numbers 114 | 115 | - (**JSON6**) Numbers can have underscores separating digits '_' these are treated as zero-width-non-breaking-space. ([Proposal](https://github.com/tc39/proposal-numeric-separator) with the exception that \_ can preceed or follow . and may be trailing.) 116 | 117 | - Numbers can be hexadecimal (base 16). ( 0x prefix ) 118 | 119 | - (**JSON6**) Numbers can be binary (base 2). (0b prefix) 120 | 121 | - (**JSON6**) Numbers can be octal (base 8). (0o prefix) 122 | 123 | - (**JSON6**) Decimal Numbers can have leading zeros. (0 prefix followed by more numbers, without a decimal) 124 | 125 | - Numbers can begin or end with a (leading or trailing) decimal point. 126 | 127 | - Numbers can include `Infinity`, `-Infinity`, `NaN`, and `-NaN`. (-NaN results as NaN) 128 | 129 | - Numbers can begin with an explicit plus sign. 130 | 131 | - Numbers can begin with multiple minus signs. For example '----123' === 123. 132 | 133 | ### Keyword Values 134 | 135 | - (**JSON6**) supports 'undefined' in addition to 'true', 'false', 'null'. 136 | 137 | 138 | ### Comments 139 | 140 | - Both inline (single-line using '//' (todo:or '#'?) ) and block (multi-line using \/\* \*\/ ) comments are allowed. 141 | - `//` comments end at a `\r` or `\n` character; They MAY also end at the end of a document, although a warning is issued at this time. 142 | - `/*` comments should be closed before the end of a document or stream flush. 143 | - `/` followed by anything else other than `/` or `*` is an error. 144 | 145 | 146 | ## Example 147 | 148 | The following is a contrived example, but it illustrates most of the features: 149 | 150 | ```js 151 | { 152 | foo: 'bar', 153 | while: true, 154 | nothing : undefined, // why not? 155 | 156 | this: 'is a \ 157 | multi-line string', 158 | 159 | thisAlso: 'is a 160 | multi-line string; but keeps newline', 161 | 162 | // this is an inline comment 163 | here: 'is another', // inline comment 164 | 165 | /* this is a block comment 166 | that continues on another line */ 167 | 168 | hex: 0xDEAD_beef, 169 | binary: 0b0110_1001, 170 | decimal: 123_456_789, 171 | octal: 0o123, 172 | half: .5, 173 | delta: +10, 174 | negative : ---123, 175 | to: Infinity, // and beyond! 176 | 177 | finally: 'a trailing comma', 178 | oh: [ 179 | "we shouldn't forget", 180 | 'arrays can have', 181 | 'trailing commas too', 182 | ], 183 | } 184 | ``` 185 | 186 | This implementation’s own [package.JSON6](package.JSON6) is more realistic: 187 | 188 | ```js 189 | // This file is written in JSON6 syntax, naturally, but npm needs a regular 190 | // JSON file, so compile via `npm run build`. Be sure to keep both in sync! 191 | 192 | { 193 | name: 'JSON6', 194 | version: '0.1.105', 195 | description: 'JSON for the ES6 era.', 196 | keywords: ['json', 'es6'], 197 | author: 'd3x0r ', 198 | contributors: [ 199 | // TODO: Should we remove this section in favor of GitHub's list? 200 | // https://github.com/d3x0r/JSON6/contributors 201 | ], 202 | main: 'lib/JSON6.js', 203 | bin: 'lib/cli.js', 204 | files: ["lib/"], 205 | dependencies: {}, 206 | devDependencies: { 207 | gulp: "^3.9.1", 208 | 'gulp-jshint': "^2.0.0", 209 | jshint: "^2.9.1", 210 | 'jshint-stylish': "^2.1.0", 211 | mocha: "^2.4.5" 212 | }, 213 | scripts: { 214 | build: 'node ./lib/cli.js -c package.JSON6', 215 | test: 'mocha --ui exports --reporter spec', 216 | // TODO: Would it be better to define these in a mocha.opts file? 217 | }, 218 | homepage: 'http://github.com/d3x0r/JSON6/', 219 | license: 'MIT', 220 | repository: { 221 | type: 'git', 222 | url: 'https://github.com/d3x0r/JSON6', 223 | }, 224 | } 225 | ``` 226 | 227 | 228 | ## Community 229 | 230 | Join the [Google Group](http://groups.google.com/group/JSON6) if you’re 231 | interested in JSON6 news, updates, and general discussion. 232 | Don’t worry, it’s very low-traffic. 233 | 234 | The [GitHub wiki](https://github.com/d3x0r/JSON6/wiki) (will be) a good place to track 235 | JSON6 support and usage. Contribute freely there! 236 | 237 | [GitHub Issues](https://github.com/d3x0r/JSON6/issues) is the place to 238 | formally propose feature requests and report bugs. Questions and general 239 | feedback are better directed at the Google Group. 240 | 241 | 242 | ## Usage 243 | 244 | This JavaScript implementation of JSON6 simply provides a `JSON6` object just 245 | like the native ES5 `JSON` object. 246 | 247 | To use from Node: 248 | 249 | ```sh 250 | npm install json-6 251 | ``` 252 | 253 | ```js 254 | var JSON6 = require('json-6'); 255 | ``` 256 | 257 | To use in the browser (adds the `JSON6` object to the global namespace): 258 | 259 | ```html 260 | 261 | ``` 262 | 263 | Then in both cases, you can simply replace native `JSON` calls with `JSON6`: 264 | 265 | ```js 266 | var obj = JSON6.parse('{unquoted:"key",trailing:"comma",}'); 267 | var str = JSON6.stringify(obj); /* uses JSON stringify, so don't have to replace */ 268 | ``` 269 | 270 | |JSON6 Methods | parameters | Description | 271 | |-----|-----|-----| 272 | |parse| (string [,reviver]) | supports all of the JSON6 features listed above, as well as the native [`reviver` argument][json-parse]. | 273 | |stringify | ( value ) | converts object to JSON. [stringify][json-stringify] | 274 | |escape | ( string ) | substitutes ", \, ', and ` with backslashed sequences. (prevent 'JSON injection') | 275 | |begin| (cb [,reviver] ) | create a JSON6 stream processor. cb is called with (value) for each value decoded from input given with write(). Optional reviver is called with each object before being passed to callback. | 276 | 277 | 278 | [json-parse]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse 279 | [json-stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify 280 | 281 | ### JSON6 Streaming 282 | 283 | A Parser that returns objects as they are encountered in a stream can be created. `JSON.begin( dataCallback, reviver );` The callback is called for each complete object in a stream of data that is passed. 284 | 285 | `JSON6.begin( cb, reviver )` returns an object with a few methods. 286 | 287 | | Method | Arguments | Description | 288 | |:---|:---|:---| 289 | | write | (string) | Parse string passed and as objects are found, invoke the callback passed to `begin()` Objects are passed through optional reviver function passed to `begin()`. | 290 | | \_write | (string,completeAtEnd) | Low level routine used internally. This does the work of parsing the passed string. Returns 0 if no object completed, 1 if there is no more data, and an object was completd, returns 2 if there is more data and a parsed object is found. if completedAtEnd is true, dangling values are returned, for example "1234" isn't known to be completed, more of the number might follow in another buffer; if completeAtEnd is passed, this iwll return as number 1234. Passing empty arguments steps to the next buffered input value. | 291 | | value | () | Returns the currently completed object. Used to get the completed object after calling \_write. | 292 | | reset | () | If `write()` or `\_write()` throws an exception, no further objects will be parsed becuase internal status is false, this resets the internal status to allow continuing using the existing parser. ( May require some work to actually work for complex cases) | 293 | 294 | 295 | ```js 296 | // This is (basically) the internal loop that write() uses. 297 | var result 298 | for( result = this._write(msg,false); result > 0; result = this._write() ) { 299 | var obj = this.value(); 300 | // call reviver with (obj) 301 | // call callback with (obj) 302 | } 303 | ``` 304 | 305 | ```js 306 | // Example code using write 307 | function dataCallback( value ) { 308 | console.log( "Value from stream:", value ); 309 | } 310 | var parser = JSON.begin( dataCallback ); 311 | 312 | parser.write( '"Hello ' ); // a broken simple value string, results as 'Hello World!' 313 | parser.write( 'World!"' ); 314 | parser.write( '{ first: 1,' ); // a broken structure 315 | parser.write( ' second : 2 }' ); 316 | parser.write( '[1234,12'); // a broken array across a value 317 | parser.write( '34,1234]'); 318 | parser.write( '1234 456 789 123 523'); // multiple single simple values that are numbers 319 | parser.write( '{a:1} {b:2} {c:3}'); // multiple objects 320 | 321 | parser.write( '1234' ); // this won't return immediately, there might be more numeric data. 322 | parser.write( '' ); // flush any pending numbers; if an object or array or string was split, throws an error; missing close. 323 | 324 | parser.write( '1234' ); 325 | parser.write( '5678 ' ); // at this point, the space will flush the number value '12345678' 326 | 327 | ``` 328 | 329 | 330 | 331 | ### Extras 332 | 333 | If you’re running this on Node, you can also register a JSON6 `require()` hook 334 | to let you `require()` `.json6` files just like you can `.json` files: 335 | 336 | ```js 337 | require('JSON-6/lib/require'); 338 | require('./path/to/foo'); // tries foo.json6 after foo.js, foo.json, etc. 339 | require('./path/to/bar.json6'); 340 | ``` 341 | 342 | This module also provides a `json6` executable (requires Node) for converting 343 | JSON6 files to JSON: 344 | 345 | ```sh 346 | json6 -c path/to/foo.json6 # generates path/to/foo.json 347 | ``` 348 | 349 | ## Other Implementations 350 | 351 | This is also implemented as part of npm [sack.vfs https://www.npmjs.com/package/sack.vfs] 352 | as a native code node.js addon. This native javascript version allows usage in browsers. 353 | 354 | ## Benchmarks 355 | 356 | This is as fast as the javascript version of Douglas Crockford's reference implementation [JSON 357 | implementation][json_parse.js] for JSON parsing. 358 | 359 | This is nearly double the speed of [JSON5 http://json5.org] implementation that inspired this (which is half the speed of Crockford's reference implementation). 360 | 361 | This is half the speed of the sack.vfs native C++ node addon implementation (which itself is half the speed of V8's native code implementation, but they can cheat and build strings directly). 362 | 363 | ## Requirements 364 | 365 | Currently `engines` is set for Node 10 or higher. 366 | 367 | However, `let`, `const`, and new unicode string support for codepoints 368 | (like `codePointAt`), are the most exotic of features used by the library. 369 | 370 | Tests may include arrow functions. 371 | 372 | For development purposes, this is tooled to always use the latest build 373 | tools, which require a minimum platform of their own. 374 | 375 | External development dependencies 376 | - rollup - for packaging and minification 377 | - various rollup support plugins 378 | - eslint - for checking best practices and code styling 379 | - acorn - a peer dep. required through eslint 380 | - mocha (^3) - automated internal test suite 381 | - chai - enable expect syntax in tests 382 | - nyc - coverage testing; make sure there's a good reason for having things 😸 383 | - core-js - polyfill unicode string support 384 | 385 | ## Development 386 | 387 | ```sh 388 | git clone https://github.com/d3x0r/json6 389 | cd json6 390 | npm install 391 | npm test 392 | ``` 393 | 394 | As the `package.json6` file states, be sure to run `npm run build` on changes 395 | to `package.json6`, since npm requires `package.json`. 396 | 397 | Feel free to [file issues](https://github.com/d3x0r/json6/issues) and submit 398 | [pull requests](https://github.com/d3x0r/JSON6/pulls) — contributions are 399 | welcome. If you do submit a pull request, please be sure to add or update the 400 | tests, and ensure that `npm test` continues to pass. 401 | 402 | ## Continuous Integration Testing 403 | 404 | Travis CI is used to automatically test the package when pushed to github. Recently .mjs tests have been 405 | added, and rather than 1) build a switch to test `mocha/test/*.js` instead of just `*`, and 2) depending on node version 406 | switch the test command which is run, the older platforms were removed from testing. 407 | 408 | The product of this should run on very old platforms also, especially `node_modules/json-6/dist/index.min.js`. 409 | 410 | 411 | ## Changelog 412 | 413 | - 1.1.5(pre) 414 | - 1.1.4 415 | - fixes benchmark test for hex number conversion 416 | - 1.1.3 417 | - fixes '\v' decoding. 418 | - fixes parsing hex numbers with a-f. 419 | - 1.1.2 420 | - Updated document about CI tests. 421 | - added tests from sack.vfs JSON6 updates. 422 | - 1.1.1 423 | - Added stringifier 424 | - emits unquoted object field names, if valid to be unquoted. 425 | - emits Infinity 426 | - emits NaN 427 | - Added forgiving '+' collection for numbers. 428 | - Improved(implemented) node module loader interface `lib/import.mjs` which enables `.json6` extension for import. 429 | - 1.0.8 430 | - throw error when streaming, and an error is encountered, persist throwing on new writes. 431 | - 1.0.7 432 | - Remove octal string escapes (Only overly clever people use those?) 433 | - Add \0 literal escape. 434 | - removed leading 0 octal interpretation. 435 | - fix trailing comma handling 436 | - clarify error reporting 437 | - Coverage completion 438 | - improve error tests 439 | - integrate with Travis. 440 | - 1.0.6 441 | - Remove leading 0 octal interpretation; code reformats, test framework improvements. 442 | - Implement automated mocha tests; fixed several edge cases 443 | - Comments that are open at the end of a document (stream flush), will throw an error; they should be closed with an end of line or `*/` as appropriate. 444 | - keywords are accepted as unquoted strings for object field names. 445 | - Improved error reporting for incomplete escape sequeneces at the end of strings. 446 | - 1.0.5 - Add interpretation of `nbsp` (codepoint 0xa0); (In the spirit of 'human readable') A 'visible' whitespace is treated as a whitespace. 447 | - 1.0.4 - error publishing (bump to republish) 448 | - 1.0.3 449 | - Fix clearing negative flag used with NaN. 450 | - update build products to produce an esm module. 451 | - 1.0.2 - Udate in Readme updated. 452 | - 1.0.1 - Fix homepage reference. 453 | - 1.0.0 - Fix bug reading surrogate pairs, and error with > 65k buffers. Release 1.0. I don't see this changing beyond the current functionality. 454 | - 0.1.127 - Fix bad shift/unshift/pop methods. 455 | - 0.1.126 - Fix handling very wide characters. Improved number parsing speed. Fix string character escapes. Update documentation to include '0o' prefix for numbers. 456 | - 0.1.125 - Fix some `let`s that were causing deoptimization 457 | - 0.1.123 - Fix `npm install json-6` in readme. Remove dev dependancies that aren't used. Fix #8 Wierd arrays [test](./tests/json6TestObjectArray.js) 458 | - 0.1.122 - Fix referencing `val.negative` that should be just `negative`. 459 | - 0.1.121 - Optimization; use `Number()` instead of `new Number()` 460 | - 0.1.120 - If a non-string is passed to parse, convert to a string using String(msg). 461 | - 0.1.119 - standardize errors; fix negative sign for -Infinity. 462 | - 0.1.118 - Fix "use strict" undefined variables string_status and exponent_digit. Issue #4. 463 | - 0.1.117 - documentation and license updates. (Issue #3) 464 | - 0.1.116 - Updated docs; Fixed stream parse issue with numbers. 465 | - 0.1.115 - Fix object key names with spaces being accepted. Fix number parsing to be more strict. 466 | - 0.1.114 - Fix true/false values. 467 | - 0.1.113 - documentation update fix. 468 | - 0.1.112 - fix streaming error at end of string, and values in some circumstances. 469 | - 0.1.111 - fix packaging error. 470 | - 0.1.110 - fix empty elements in arrays. `[,]` = `[]` not `[undefined]`. improve test. 471 | - 0.1.109 - fix redundant result with certain buffers. 472 | - 0.1.108 - rename 'add' to 'write' for compatibilty with other sack.vfs JSON6 parser. 473 | - 0.1.107 - fix variable used for gathering Strings that caused permanent error 474 | - 0.1.106 - fix handling whitespace after keyword 475 | - 0.1.105 - Add a streaming interface. 476 | - 0.1.104 - Readme updates. 477 | - 0.1.103 - Add underscore as a zero-space-non-breaking-whitespace for numbers. 478 | 479 | 480 | ## License 481 | 482 | MIT. See [LICENSE.md](./LICENSE.md) for details. 483 | 484 | 485 | ## Credits 486 | 487 | (http://github.com/json5/json5) Inspring this project. 488 | 489 | [json_parse.js]: https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js 490 | -------------------------------------------------------------------------------- /build/es5.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('core-js/features/string/code-point-at'); 3 | require('core-js/features/string/from-code-point'); 4 | 5 | const JSON6 = require('../lib'); 6 | 7 | module.exports = JSON6; 8 | -------------------------------------------------------------------------------- /build/package.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const JSON5 = require('../lib'); 6 | 7 | const pkg = require('../package.json'); 8 | 9 | const pkg5 = `// This is a generated file. Do not edit. 10 | ${JSON5.stringify(pkg, null, 2)} 11 | 12 | `; 13 | 14 | fs.writeFileSync(path.resolve(__dirname, '..', 'package.json5'), pkg5); 15 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | // cli.js 5 | // JSON6 command-line interface. 6 | // 7 | // This is pretty minimal for now; just supports compiling files via `-c`. 8 | // TODO More useful functionality, like output path, watch, etc.? 9 | 10 | const FS = require('fs'); 11 | const JSON6 = require('./json6'); 12 | const Path = require('path'); 13 | 14 | const USAGE = [ 15 | 'Usage: json6 -c path/to/file.json6 ...', 16 | 'Compiles JSON6 files into sibling JSON files with the same basenames.', 17 | ].join('\n'); 18 | 19 | // if valid, args look like [node, json6, -c, file1, file2, ...] 20 | const args = process.argv; 21 | 22 | if (args.length < 4 || args[2] !== '-c') { 23 | console.error(USAGE); 24 | process.exit(1); 25 | } 26 | 27 | const cwd = process.cwd(); 28 | const files = args.slice(3); 29 | 30 | // iterate over each file and convert JSON6 files to JSON: 31 | files.forEach(function (file) { 32 | const json6Path = Path.resolve(cwd, file); 33 | const basename = Path.basename(json6Path, '.json6'); 34 | const dirname = Path.dirname(json6Path); 35 | 36 | const json6 = FS.readFileSync(json6Path, 'utf8'); 37 | const obj = JSON6.parse(json6); 38 | const json = JSON.stringify(obj, null, 4) + '\n'; // 4 spaces; TODO configurable? 39 | 40 | const jsonPath = Path.join(dirname, basename + '.json'); 41 | FS.writeFileSync(jsonPath, json, 'utf8'); 42 | }); 43 | -------------------------------------------------------------------------------- /lib/import.mjs: -------------------------------------------------------------------------------- 1 | // import.mjs 2 | // Node.js only: adds a import() hook for .json6 files, just like the native 3 | // hook for .json files. 4 | // 5 | // Usage: 6 | // import {default as config} from "config.json6"; 7 | 8 | 9 | import fs from "fs"; 10 | import url from "url"; 11 | import path from "path"; 12 | 13 | /** 14 | * @param {string} url 15 | * @param {Object} context (currently empty) 16 | * @param {Function} defaultGetFormat 17 | * @returns {Promise<{ format: string }>} 18 | */ 19 | export async function getFormat(url, context, defaultGetFormat) { 20 | const exten = path.extname( url ); 21 | //if( exten === '' ) return { format:'module' } 22 | if( exten === ".json6" ){ 23 | return { format: 'module' }; 24 | } 25 | return defaultGetFormat(url,context ); 26 | } 27 | 28 | /** 29 | * @param {string} url 30 | * @param {{ format: string }} context 31 | * @param {Function} defaultGetSource 32 | * @returns {Promise<{ source: !(string | SharedArrayBuffer | Uint8Array) }>} 33 | */ 34 | export async function getSource(urlin, context, defaultGetSource) { 35 | const exten = path.extname( urlin ); 36 | if( exten === ".json6" ){ 37 | //const { format } = context; 38 | const file = url.fileURLToPath(urlin); 39 | return { 40 | source: fs.readFileSync(file, 'utf8'), 41 | }; 42 | } 43 | // Defer to Node.js for all other URLs. 44 | return defaultGetSource(urlin, context, defaultGetSource); 45 | } 46 | 47 | /** 48 | * @param {!(string | SharedArrayBuffer | Uint8Array)} source 49 | * @param {{ 50 | * format: string, 51 | * url: string, 52 | * }} context 53 | * @param {Function} defaultTransformSource 54 | * @returns {Promise<{ source: !(string | SharedArrayBuffer | Uint8Array) }>} 55 | */ 56 | export async function transformSource(source, context, defaultTransformSource) { 57 | const exten = path.extname( context.url ); 58 | if( exten === ".json6" ){ 59 | return { 60 | source: "const data = JSON6.parse( '" + escape(source) + "'); export default data;", 61 | }; 62 | } 63 | return defaultTransformSource(source, context, defaultTransformSource); 64 | } 65 | 66 | function escape(string) { 67 | let output = ''; 68 | if( !string ) return string; 69 | for( let n = 0; n < string.length; n++ ) { 70 | const ch = string[n]; 71 | if( ch === '\n' ){ 72 | output += "\\n"; 73 | } else { 74 | if( ( ch === '"' ) || ( ch === '\\' ) || ( ch === '`' )|| ( ch === '\'' )) { 75 | output += '\\'; 76 | } 77 | output += ch; 78 | } 79 | } 80 | return output; 81 | } 82 | 83 | /** 84 | * @returns {string} Code to run before application startup 85 | */ 86 | // Preloads JSON6 as a global resource; which is then used in the transformed source above. 87 | export function getGlobalPreloadCode() { 88 | return `\ 89 | const { createRequire } = getBuiltin('module'); 90 | const requireJSON6 = createRequire('${escape(url.fileURLToPath( import.meta.url ))}'); 91 | globalThis.JSON6 = requireJSON6( "../lib/index.js" );`; 92 | } 93 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = require('./json6.js'); 3 | -------------------------------------------------------------------------------- /lib/json6.js: -------------------------------------------------------------------------------- 1 | // json6.js 2 | // JSON for Humans. See README.md for details. 3 | // 4 | // This file is based off of https://github.com/d3x0r/sack ./src/netlib/html5.websocket/json6_parser.c 5 | // 6 | "use strict"; 7 | const version = "1.1.1"; 8 | const VALUE_UNDEFINED = -1; 9 | const VALUE_UNSET = 0; 10 | const VALUE_NULL = 1; 11 | const VALUE_TRUE = 2; 12 | const VALUE_FALSE = 3; 13 | const VALUE_STRING = 4; 14 | const VALUE_NUMBER = 5; 15 | const VALUE_OBJECT = 6; 16 | const VALUE_ARRAY = 7; 17 | const VALUE_NEG_NAN = 8; 18 | const VALUE_NAN = 9; 19 | const VALUE_NEG_INFINITY = 10; 20 | const VALUE_INFINITY = 11; 21 | // const VALUE_DATE = 12 // unused yet 22 | const VALUE_EMPTY = 13; // [,] makes an array with 'empty item' 23 | 24 | const WORD_POS_RESET = 0; 25 | const WORD_POS_TRUE_1 = 1; 26 | const WORD_POS_TRUE_2 = 2; 27 | const WORD_POS_TRUE_3 = 3; 28 | const WORD_POS_FALSE_1 = 5; 29 | const WORD_POS_FALSE_2 = 6; 30 | const WORD_POS_FALSE_3 = 7; 31 | const WORD_POS_FALSE_4 = 8; 32 | const WORD_POS_NULL_1 = 9; 33 | const WORD_POS_NULL_2 = 10; 34 | const WORD_POS_NULL_3 = 11; 35 | const WORD_POS_UNDEFINED_1 = 12; 36 | const WORD_POS_UNDEFINED_2 = 13; 37 | const WORD_POS_UNDEFINED_3 = 14; 38 | const WORD_POS_UNDEFINED_4 = 15; 39 | const WORD_POS_UNDEFINED_5 = 16; 40 | const WORD_POS_UNDEFINED_6 = 17; 41 | const WORD_POS_UNDEFINED_7 = 18; 42 | const WORD_POS_UNDEFINED_8 = 19; 43 | const WORD_POS_NAN_1 = 20; 44 | const WORD_POS_NAN_2 = 21; 45 | const WORD_POS_INFINITY_1 = 22; 46 | const WORD_POS_INFINITY_2 = 23; 47 | const WORD_POS_INFINITY_3 = 24; 48 | const WORD_POS_INFINITY_4 = 25; 49 | const WORD_POS_INFINITY_5 = 26; 50 | const WORD_POS_INFINITY_6 = 27; 51 | const WORD_POS_INFINITY_7 = 28; 52 | 53 | const WORD_POS_FIELD = 29; 54 | const WORD_POS_AFTER_FIELD = 30; 55 | const WORD_POS_END = 31; 56 | 57 | const CONTEXT_UNKNOWN = 0; 58 | const CONTEXT_IN_ARRAY = 1; 59 | // const CONTEXT_IN_OBJECT = 2 60 | const CONTEXT_OBJECT_FIELD = 3; 61 | const CONTEXT_OBJECT_FIELD_VALUE = 4; 62 | 63 | const contexts = []; 64 | function getContext() { 65 | return contexts.pop() || { 66 | context : CONTEXT_UNKNOWN, 67 | elements : null, 68 | element_array : null 69 | }; 70 | } 71 | function dropContext(ctx) { 72 | contexts.push( ctx ); 73 | } 74 | 75 | const buffers = []; 76 | function getBuffer() { 77 | let buf = buffers.pop(); 78 | if( !buf ) buf = { buf:null, n:0 }; 79 | else buf.n = 0; 80 | return buf; 81 | } 82 | function dropBuffer(buf) { 83 | buffers.push( buf ); 84 | } 85 | 86 | const JSON6 = typeof exports === 'object' 87 | ? exports 88 | // istanbul ignore next 89 | : {}; 90 | 91 | /* 92 | let _DEBUG_LL = true; 93 | let _DEBUG_PARSING = true; 94 | let _DEBUG_PARSING_STACK = true; 95 | 96 | const log = function(type) { 97 | if (type === '_DEBUG_PARSING' && !_DEBUG_PARSING) { 98 | return; 99 | } 100 | if (type === '_DEBUG_PARSING_STACK' && !_DEBUG_PARSING_STACK) { 101 | return; 102 | } 103 | if (type === '_DEBUG_LL' && !_DEBUG_LL) { 104 | return; 105 | } 106 | console.log.apply(console, [].slice.call(arguments, 1)); 107 | }; 108 | */ 109 | 110 | JSON6.escape = function(string) { 111 | let output = ''; 112 | if( !string ) return string; 113 | for( let n = 0; n < string.length; n++ ) { 114 | const ch = string[n]; 115 | if( ( ch == '"' ) || ( ch == '\\' ) || ( ch == '`' )|| ( ch == '\'' )) { 116 | output += '\\'; 117 | } 118 | output += ch; 119 | } 120 | return output; 121 | }; 122 | 123 | 124 | JSON6.begin = function( cb, reviver ) { 125 | 126 | const val = { name : null, // name of this value (if it's contained in an object) 127 | value_type: VALUE_UNSET, // value from above indiciating the type of this value 128 | string : '', // the string value of this value (strings and number types only) 129 | contains : null, 130 | }; 131 | 132 | const pos = { line:1, col:1 }; 133 | let n = 0; 134 | let word = WORD_POS_RESET, 135 | status = true, 136 | negative = false, 137 | result = null, 138 | elements = undefined, 139 | element_array = [], 140 | parse_context = CONTEXT_UNKNOWN, 141 | comment = 0, 142 | fromHex = false, 143 | decimal = false, 144 | exponent = false, 145 | exponent_sign = false, 146 | exponent_digit = false, 147 | gatheringStringFirstChar = null, 148 | gatheringString = false, 149 | gatheringNumber = false, 150 | stringEscape = false, 151 | cr_escaped = false, 152 | unicodeWide = false, 153 | stringUnicode = false, 154 | stringHex = false, 155 | hex_char = 0, 156 | hex_char_len = 0, 157 | completed = false 158 | ; 159 | 160 | const context_stack = { 161 | first : null, 162 | last : null, 163 | saved : null, 164 | push(node) { 165 | let recover = this.saved; 166 | if( recover ) { this.saved = recover.next; recover.node = node; recover.next = null; recover.prior = this.last; } 167 | else { recover = { node : node, next : null, prior : this.last }; } 168 | if( !this.last ) this.first = recover; 169 | this.last = recover; 170 | }, 171 | pop() { 172 | const result = this.last; 173 | if( !(this.last = result.prior ) ) this.first = null; 174 | result.next = this.saved; this.saved = result; 175 | return result.node; 176 | } 177 | }; 178 | 179 | const inQueue = { 180 | first : null, 181 | last : null, 182 | saved : null, 183 | push(node) { 184 | let recover = this.saved; 185 | if( recover ) {this.saved = recover.next; recover.node = node; recover.next = null; recover.prior = this.last; } 186 | else { recover = { node : node, next : null, prior : this.last }; } 187 | if( !this.last ) this.first = recover; 188 | else this.last.next = recover; 189 | this.last = recover; 190 | }, 191 | shift() { 192 | const result = this.first; 193 | if( !result ) return null; 194 | this.first = result.next; 195 | if( !this.first ) this.last = null; 196 | result.next = this.saved; 197 | this.saved = result; 198 | // node is in saved... 199 | return result.node; 200 | }, 201 | unshift(node) { 202 | // usage in this module, recover will ALWAYS have a saved to use. 203 | const recover = this.saved; 204 | //if( recover ) { 205 | this.saved = recover.next; recover.node = node; recover.next = this.first; recover.prior = null; 206 | //} else { recover = { node : node, next : this.first, prior : null }; } 207 | if( !this.first ) this.last = recover; 208 | this.first = recover; 209 | } 210 | }; 211 | 212 | function throwEndError( leader /* , c */ ) { 213 | throw new Error( `${leader} at ${n} [${pos.line}:${pos.col}]`); 214 | } 215 | 216 | 217 | return { 218 | finalError() { 219 | if( comment !== 0 ) { // most of the time everything's good. 220 | switch (comment) { 221 | case 1: 222 | return throwEndError( "Comment began at end of document" ); 223 | case 2: 224 | console.log( "Warning: '//' comment without end of line ended document" ); 225 | break; 226 | case 3: 227 | return throwEndError( "Open comment '/*' is missing close at end of document" ); 228 | case 4: 229 | return throwEndError( "Incomplete '/* *' close at end of document" ); 230 | } 231 | } 232 | if( gatheringString ) throwEndError( "Incomplete string" ); 233 | }, 234 | value() { 235 | this.finalError(); 236 | const r = result; 237 | result = undefined; 238 | return r; 239 | }, 240 | reset() { 241 | word = WORD_POS_RESET; 242 | status = true; 243 | if( inQueue.last ) inQueue.last.next = inQueue.save; 244 | inQueue.save = inQueue.first; 245 | inQueue.first = inQueue.last = null; 246 | if( context_stack.last ) context_stack.last.next = context_stack.save; 247 | context_stack.save = inQueue.first; 248 | context_stack.first = context_stack.last = null;//= []; 249 | element_array = null; 250 | elements = undefined; 251 | parse_context = CONTEXT_UNKNOWN; 252 | val.value_type = VALUE_UNSET; 253 | val.name = null; 254 | val.string = ''; 255 | pos.line = 1; 256 | pos.col = 1; 257 | negative = false; 258 | comment = 0; 259 | completed = false; 260 | gatheringString = false; 261 | stringEscape = false; // string stringEscape intro 262 | cr_escaped = false; // carraige return escaped 263 | //stringUnicode = false; // reading \u 264 | //unicodeWide = false; // reading \u{} in string 265 | //stringHex = false; // reading \x in string 266 | }, 267 | write(msg) { 268 | let retcode; 269 | if( msg !== undefined && typeof msg !== "string") msg = String(msg); 270 | if( !status ) throw new Error( "Parser is in an error state, please reset." ); 271 | for( retcode = this._write(msg,false); retcode > 0; retcode = this._write() ) { 272 | this.finalError(); 273 | if( typeof reviver === 'function' ) (function walk(holder, key) { 274 | const value = holder[key]; 275 | if (value && typeof value === 'object') { 276 | for (const k in value) { 277 | if (Object.prototype.hasOwnProperty.call(value, k)) { 278 | const v = walk(value, k); 279 | if (v !== undefined) { 280 | value[k] = v; 281 | } else { 282 | delete value[k]; 283 | } 284 | } 285 | } 286 | } 287 | return reviver.call(holder, key, value); 288 | }({'': result}, '')); 289 | cb( result ); 290 | result = undefined; 291 | 292 | if( retcode < 2 ) 293 | break; 294 | } 295 | if( retcode ) 296 | this.finalError(); 297 | }, 298 | _write(msg,complete_at_end) { 299 | let input; 300 | let buf; 301 | let retval = 0; 302 | 303 | function throwError( leader, c ) { 304 | throw new Error( `${leader} '${String.fromCodePoint( c )}' unexpected at ${n} (near '${buf.substr(n>4?(n-4):0,n>4?3:(n-1))}[${String.fromCodePoint( c )}]${buf.substr(n, 10)}') [${pos.line}:${pos.col}]`); 305 | } 306 | 307 | function RESET_VAL() { 308 | val.value_type = VALUE_UNSET; 309 | val.string = ''; 310 | } 311 | 312 | function arrayPush() { 313 | switch( val.value_type ){ 314 | case VALUE_NUMBER: 315 | element_array.push( (negative?-1:1) * Number( val.string ) ); 316 | break; 317 | case VALUE_STRING: 318 | element_array.push( val.string ); 319 | break; 320 | case VALUE_TRUE: 321 | element_array.push( true ); 322 | break; 323 | case VALUE_FALSE: 324 | element_array.push( false ); 325 | break; 326 | case VALUE_NEG_NAN: 327 | element_array.push( -NaN ); 328 | break; 329 | case VALUE_NAN: 330 | element_array.push( NaN ); 331 | break; 332 | case VALUE_NEG_INFINITY: 333 | element_array.push( -Infinity ); 334 | break; 335 | case VALUE_INFINITY: 336 | element_array.push( Infinity ); 337 | break; 338 | case VALUE_NULL: 339 | element_array.push( null ); 340 | break; 341 | case VALUE_UNDEFINED: 342 | element_array.push( undefined ); 343 | break; 344 | case VALUE_EMPTY: 345 | element_array.push( undefined ); 346 | delete element_array[element_array.length-1]; 347 | break; 348 | case VALUE_OBJECT: 349 | element_array.push( val.contains ); 350 | break; 351 | case VALUE_ARRAY: 352 | element_array.push( val.contains ); 353 | break; 354 | } 355 | } 356 | function objectPush() { 357 | switch( val.value_type ){ 358 | case VALUE_NUMBER: 359 | elements[val.name] = ((negative?-1:1) * Number( val.string )); 360 | break; 361 | case VALUE_STRING: 362 | elements[val.name] = ( val.string ); 363 | break; 364 | case VALUE_TRUE: 365 | elements[val.name] = ( true ); 366 | break; 367 | case VALUE_FALSE: 368 | elements[val.name] = ( false ); 369 | break; 370 | case VALUE_NEG_NAN: 371 | elements[val.name] = ( -NaN ); 372 | break; 373 | case VALUE_NAN: 374 | elements[val.name] = ( NaN ); 375 | break; 376 | case VALUE_NEG_INFINITY: 377 | elements[val.name] = ( -Infinity ); 378 | break; 379 | case VALUE_INFINITY: 380 | elements[val.name] = ( Infinity ); 381 | break; 382 | case VALUE_NULL: 383 | elements[val.name] = ( null ); 384 | break; 385 | case VALUE_UNDEFINED: 386 | elements[val.name] = ( undefined ); 387 | break; 388 | case VALUE_OBJECT: 389 | elements[val.name] = val.contains; 390 | break; 391 | case VALUE_ARRAY: 392 | elements[val.name] = val.contains; 393 | break; 394 | } 395 | } 396 | 397 | function gatherString( start_c ) { 398 | let retval = 0; 399 | while( retval == 0 && ( n < buf.length ) ) { 400 | let str = buf.charAt(n); 401 | const cInt = buf.codePointAt(n++); 402 | if( cInt >= 0x10000 ) { str += buf.charAt(n); n++; } 403 | //console.log( "gathering....", stringEscape, str, cInt, unicodeWide, stringHex, stringUnicode, hex_char_len ); 404 | pos.col++; 405 | if( cInt == start_c ) {//( cInt == 34/*'"'*/ ) || ( cInt == 39/*'\''*/ ) || ( cInt == 96/*'`'*/ ) ) 406 | if( stringEscape ) { 407 | if( stringHex ) 408 | throwError( "Incomplete hexidecimal sequence", cInt ); 409 | else if( unicodeWide ) 410 | throwError( "Incomplete long unicode sequence", cInt ); 411 | else if( stringUnicode ) 412 | throwError( "Incomplete unicode sequence", cInt ); 413 | 414 | if( cr_escaped ) { 415 | cr_escaped = false; // \\ \r ' :end string, the backslash was used for \r 416 | retval = 1; // complete string. 417 | } else val.string += str; // escaped start quote 418 | stringEscape = false; 419 | } 420 | else { 421 | // quote matches, not escaped, and not processing escape... 422 | retval = 1; 423 | } 424 | } 425 | 426 | else if( stringEscape ) { 427 | if( unicodeWide ) { 428 | if( cInt == 125/*'}'*/ ) { 429 | val.string += String.fromCodePoint( hex_char ); 430 | unicodeWide = false; 431 | stringUnicode = false; 432 | stringEscape = false; 433 | continue; 434 | } 435 | hex_char *= 16; 436 | if( cInt >= 48/*'0'*/ && cInt <= 57/*'9'*/ ) hex_char += cInt - 0x30; 437 | else if( cInt >= 65/*'A'*/ && cInt <= 70/*'F'*/ ) hex_char += ( cInt - 65 ) + 10; 438 | else if( cInt >= 97/*'a'*/ && cInt <= 102/*'f'*/ ) hex_char += ( cInt - 97 ) + 10; 439 | else { 440 | throwError( "(escaped character, parsing hex of \\u)", cInt ); 441 | } 442 | continue; 443 | } 444 | else if( stringHex || stringUnicode ) { 445 | if( hex_char_len === 0 && cInt === 123/*'{'*/ ) { 446 | unicodeWide = true; 447 | continue; 448 | } 449 | hex_char *= 16; 450 | if( cInt >= 48/*'0'*/ && cInt <= 57/*'9'*/ ) hex_char += cInt - 0x30; 451 | else if( cInt >= 65/*'A'*/ && cInt <= 70/*'F'*/ ) hex_char += ( cInt - 65 ) + 10; 452 | else if( cInt >= 97/*'a'*/ && cInt <= 102/*'f'*/ ) hex_char += ( cInt - 97 ) + 10; 453 | else { 454 | throwError( stringUnicode?"(escaped character, parsing hex of \\u)":"(escaped character, parsing hex of \\x)", cInt ); 455 | } 456 | hex_char_len++; 457 | if( stringUnicode ) { 458 | if( hex_char_len == 4 ) { 459 | val.string += String.fromCodePoint( hex_char ); 460 | stringUnicode = false; 461 | stringEscape = false; 462 | } 463 | } 464 | else if( hex_char_len == 2 ) { 465 | val.string += String.fromCodePoint( hex_char ); 466 | stringHex = false; 467 | stringEscape = false; 468 | } 469 | continue; 470 | } 471 | switch( cInt ) { 472 | case 13/*'\r'*/: 473 | cr_escaped = true; 474 | pos.col = 1; 475 | continue; 476 | case 0x2028: // LS (Line separator) 477 | case 0x2029: // PS (paragraph separator) 478 | pos.col = 1; // no return to get newline reset, so reset line pos. 479 | // Fallthrough 480 | case 10/*'\n'*/: 481 | if( cr_escaped ) { 482 | // \\ \r \n 483 | cr_escaped = false; 484 | } else { 485 | // \\ \n 486 | pos.col = 1; 487 | } 488 | pos.line++; 489 | break; 490 | case 116/*'t'*/: 491 | val.string += '\t'; 492 | break; 493 | case 98/*'b'*/: 494 | val.string += '\b'; 495 | break; 496 | case 48/*'0'*/: 497 | val.string += '\0'; 498 | break; 499 | case 110/*'n'*/: 500 | val.string += '\n'; 501 | break; 502 | case 114/*'r'*/: 503 | val.string += '\r'; 504 | break; 505 | case 102/*'f'*/: 506 | val.string += '\f'; 507 | break; 508 | case 118/*'v'*/: 509 | val.string += '\v'; 510 | break; 511 | case 120/*'x'*/: 512 | stringHex = true; 513 | hex_char_len = 0; 514 | hex_char = 0; 515 | continue; 516 | case 117/*'u'*/: 517 | stringUnicode = true; 518 | hex_char_len = 0; 519 | hex_char = 0; 520 | continue; 521 | default: 522 | val.string += str; 523 | break; 524 | } 525 | //console.log( "other..." ); 526 | stringEscape = false; 527 | } 528 | else if( cInt === 92/*'\\'*/ ) { 529 | stringEscape = true; 530 | } 531 | else { 532 | if( cr_escaped ) { 533 | cr_escaped = false; 534 | // \\ \r 535 | pos.line++; 536 | pos.col = 2; // newline, plus one character. 537 | } 538 | val.string += str; 539 | } 540 | } 541 | return retval; 542 | } 543 | 544 | 545 | function collectNumber() { 546 | let _n; 547 | while( (_n = n) < buf.length ) { 548 | const str = buf.charAt(_n); 549 | const cInt = buf.codePointAt(n++); 550 | if( cInt >= 0x10000 ) { 551 | throwError( "fault while parsing number;", cInt ); 552 | } 553 | //log('_DEBUG_PARSING', "in getting number:", n, cInt, String.fromCodePoint(cInt) ); 554 | if( cInt == 95 /*_*/ ) 555 | continue; 556 | pos.col++; 557 | // leading zeros should be forbidden. 558 | if( cInt >= 48/*'0'*/ && cInt <= 57/*'9'*/ ) { 559 | if( exponent ) { 560 | exponent_digit = true; 561 | } 562 | val.string += str; 563 | } else if( cInt == 45/*'-'*/ || cInt == 43/*'+'*/ ) { 564 | if( val.string.length == 0 || ( exponent && !exponent_sign && !exponent_digit ) ) { 565 | val.string += str; 566 | exponent_sign = true; 567 | } else { 568 | status = false; 569 | throwError( "fault while parsing number;", cInt ); 570 | // break; 571 | } 572 | } else if( cInt == 46/*'.'*/ ) { 573 | if( !decimal && !fromHex && !exponent ) { 574 | val.string += str; 575 | decimal = true; 576 | } else { 577 | status = false; 578 | throwError( "fault while parsing number;", cInt ); 579 | // break; 580 | } 581 | } else if( fromHex && ( ( ( cInt >= 95/*'a'*/ ) && ( cInt <= 102/*'f'*/ ) ) || 582 | ( ( cInt >= 65/*'A'*/ ) && ( cInt <= 70/*'F'*/ ) ) ) ) { 583 | val.string += str; 584 | } else if( cInt == 120/*'x'*/ || cInt == 98/*'b'*/ || cInt == 111/*'o'*/ 585 | || cInt == 88/*'X'*/ || cInt == 66/*'B'*/ || cInt == 79/*'O'*/ ) { 586 | // hex conversion. 587 | if( !fromHex && val.string == '0' ) { 588 | fromHex = true; 589 | val.string += str; 590 | } 591 | else { 592 | status = false; 593 | throwError( "fault while parsing number;", cInt ); 594 | // break; 595 | } 596 | } else if( ( cInt == 101/*'e'*/ ) || ( cInt == 69/*'E'*/ ) ) { 597 | if( !exponent ) { 598 | val.string += str; 599 | exponent = true; 600 | } else { 601 | status = false; 602 | throwError( "fault while parsing number;", cInt ); 603 | // break; 604 | } 605 | } else { 606 | if( cInt == 32/*' '*/ || cInt == 160/*   */ || cInt == 13 || cInt == 10 || cInt == 9 607 | || cInt == 0xFEFF || cInt == 44/*','*/ || cInt == 125/*'}'*/ || cInt == 93/*']'*/ 608 | || cInt == 58/*':'*/ ) { 609 | break; 610 | } 611 | else { 612 | if( complete_at_end ) { 613 | status = false; 614 | throwError( "fault while parsing number;", cInt ); 615 | } 616 | break; 617 | } 618 | } 619 | } 620 | n = _n; 621 | 622 | if( (!complete_at_end) && n == buf.length ) { 623 | gatheringNumber = true; 624 | } 625 | else { 626 | gatheringNumber = false; 627 | val.value_type = VALUE_NUMBER; 628 | if( parse_context == CONTEXT_UNKNOWN ) { 629 | completed = true; 630 | } 631 | } 632 | } 633 | 634 | if( !status ) 635 | return -1; 636 | if( msg && msg.length ) { 637 | input = getBuffer(); 638 | input.buf = msg; 639 | inQueue.push( input ); 640 | } else { 641 | if( gatheringNumber ) { 642 | //console.log( "Force completed.") 643 | gatheringNumber = false; 644 | val.value_type = VALUE_NUMBER; 645 | if( parse_context == CONTEXT_UNKNOWN ) { 646 | completed = true; 647 | } else { 648 | throw new Error( "context stack is not empty at flush" ); 649 | } 650 | retval = 1; // if returning buffers, then obviously there's more in this one. 651 | } 652 | } 653 | 654 | while( status && ( input = inQueue.shift() ) ) { 655 | n = input.n; 656 | buf = input.buf; 657 | if( gatheringString ) { 658 | const string_status = gatherString( gatheringStringFirstChar ); 659 | if( string_status > 0 ) { 660 | gatheringString = false; 661 | val.value_type = VALUE_STRING; 662 | } 663 | } 664 | if( gatheringNumber ) { 665 | collectNumber(); 666 | } 667 | 668 | while( !completed && status && ( n < buf.length ) ) { 669 | let str = buf.charAt(n); 670 | const cInt = buf.codePointAt(n++); 671 | if( cInt >= 0x10000 ) { str += buf.charAt(n); n++; } 672 | //// log('_DEBUG_PARSING', "parsing at ", cInt, str ); 673 | //log('_DEBUG_LL', "processing: ", cInt, str, pos, comment, parse_context, word, val ); 674 | pos.col++; 675 | if( comment ) { // '/' 676 | if( comment == 1 ) { // '/' 677 | if( cInt == 42/*'*'*/ ) { comment = 3; } // '/*' 678 | else if( cInt != 47/*'/'*/ ) { // '//'(NOT) 679 | throwError( "fault while parsing;", cInt ); 680 | } 681 | else comment = 2; // '//' (valid) 682 | } 683 | else if( comment == 2 ) { // '// ...' 684 | if( cInt == 10/*'\n'*/ 685 | || cInt == 13/*'\r'*/ ) comment = 0; 686 | } 687 | else if( comment == 3 ){ // '/*... ' 688 | if( cInt == 42/*'*'*/ ) comment = 4; 689 | } 690 | else { // if( comment == 4 ) { // '/* ... *' 691 | if( cInt == 47/*'/'*/ ) comment = 0; 692 | else comment = 3; // any other char, goto expect * to close */ 693 | } 694 | continue; 695 | } 696 | switch( cInt ) { 697 | case 47/*'/'*/: 698 | comment = 1; 699 | break; 700 | case 123/*'{'*/: 701 | if( word == WORD_POS_FIELD || word == WORD_POS_AFTER_FIELD || ( parse_context == CONTEXT_OBJECT_FIELD && word == WORD_POS_RESET ) ) { 702 | throwError( "fault while parsing; getting field name unexpected ", cInt ); 703 | // break; 704 | } 705 | { 706 | const old_context = getContext(); 707 | //log('_DEBUG_PARSING', "Begin a new object; previously pushed into elements; but wait until trailing comma or close previously:%d", val.value_type ); 708 | 709 | val.value_type = VALUE_OBJECT; 710 | const tmpobj = {}; 711 | if( parse_context == CONTEXT_UNKNOWN ) 712 | result = elements = tmpobj; 713 | 714 | old_context.context = parse_context; 715 | old_context.elements = elements; 716 | old_context.element_array = element_array; 717 | old_context.name = val.name; 718 | elements = tmpobj; 719 | //log('_DEBUG_PARSING_STACK',"push context (open object): ", context_stack.length ); 720 | context_stack.push( old_context ); 721 | RESET_VAL(); 722 | parse_context = CONTEXT_OBJECT_FIELD; 723 | } 724 | break; 725 | 726 | case 91/*'['*/: 727 | if( parse_context == CONTEXT_OBJECT_FIELD || word == WORD_POS_FIELD || word == WORD_POS_AFTER_FIELD ) { 728 | throwError( "Fault while parsing; while getting field name unexpected", cInt ); 729 | // break; 730 | } 731 | if( val.value_type == VALUE_UNSET || val.value_type == VALUE_UNDEFINED ) 732 | { 733 | const old_context = getContext(); 734 | //log('_DEBUG_PARSING', "Begin a new array; previously pushed into elements; but wait until trailing comma or close previously:%d", val.value_type ); 735 | 736 | val.value_type = VALUE_ARRAY; 737 | const tmparr = []; 738 | if( parse_context == CONTEXT_UNKNOWN ) 739 | result = element_array = tmparr; 740 | //else if( parse_context == CONTEXT_IN_ARRAY ) 741 | // element_array.push( tmparr ); 742 | else if( parse_context == CONTEXT_OBJECT_FIELD_VALUE ) 743 | elements[val.name] = tmparr; 744 | 745 | old_context.context = parse_context; 746 | old_context.elements = elements; 747 | old_context.element_array = element_array; 748 | old_context.name = val.name; 749 | element_array = tmparr; 750 | //log('_DEBUG_PARSING_STACK', "push context (open array): ", context_stack.length ); 751 | context_stack.push( old_context ); 752 | 753 | RESET_VAL(); 754 | parse_context = CONTEXT_IN_ARRAY; 755 | } else { 756 | throwError( "Unexpected array open after previous value", cInt ); 757 | } 758 | break; 759 | 760 | case 58/*':'*/: 761 | ////log('_DEBUG_PARSING', "colon context:", parse_context ); 762 | if( parse_context == CONTEXT_OBJECT_FIELD ) { 763 | word = WORD_POS_RESET; 764 | val.name = val.string; 765 | val.string = ''; 766 | parse_context = CONTEXT_OBJECT_FIELD_VALUE; 767 | val.value_type = VALUE_UNSET; 768 | } 769 | else { 770 | if( parse_context == CONTEXT_IN_ARRAY ) 771 | throwError( "(in array, got colon out of string):parsing fault;", cInt ); 772 | else 773 | throwError( "(outside any object, got colon out of string):parsing fault;", cInt ); 774 | } 775 | break; 776 | case 125/*'}'*/: 777 | ////log('_DEBUG_PARSING', "close bracket context:", word, parse_context ); 778 | if( word == WORD_POS_END ) { 779 | // allow starting a new word 780 | word = WORD_POS_RESET; 781 | } 782 | // coming back after pushing an array or sub-object will reset the context to FIELD, so an end with a field should still push value. 783 | if( ( parse_context == CONTEXT_OBJECT_FIELD ) ) { 784 | //log('_DEBUG_PARSING', "close object; empty object %d", val.value_type ); 785 | //RESET_VAL(); 786 | val.value_type = VALUE_OBJECT; 787 | val.contains = elements; 788 | 789 | const old_context = context_stack.pop(); 790 | //log('_DEBUG_PARSING_STACK',"object pop stack (close obj)", context_stack.length, old_context ); 791 | val.name = old_context.name; 792 | parse_context = old_context.context; // this will restore as IN_ARRAY or OBJECT_FIELD 793 | elements = old_context.elements; 794 | element_array = old_context.element_array; 795 | dropContext( old_context ); 796 | if( parse_context == CONTEXT_UNKNOWN ) { 797 | completed = true; 798 | } 799 | } 800 | else if( ( parse_context == CONTEXT_OBJECT_FIELD_VALUE ) ) { 801 | // first, add the last value 802 | //log('_DEBUG_PARSING', "close object; push item '%s' %d", val.name, val.value_type ); 803 | if( val.value_type != VALUE_UNSET ) { 804 | objectPush(); 805 | } else { 806 | throwError( "Fault while parsing field value, close with no value", cInt ); 807 | } 808 | val.value_type = VALUE_OBJECT; 809 | val.contains = elements; 810 | 811 | const old_context = context_stack.pop(); 812 | //log('_DEBUG_PARSING_STACK',"object pop stack (close object)", context_stack.length, old_context ); 813 | val.name = old_context.name; 814 | parse_context = old_context.context; // this will restore as IN_ARRAY or OBJECT_FIELD 815 | elements = old_context.elements; 816 | element_array = old_context.element_array; 817 | dropContext( old_context ); 818 | if( parse_context == CONTEXT_UNKNOWN ) { 819 | completed = true; 820 | } 821 | } 822 | else { 823 | throwError( "Fault while parsing; unexpected", cInt ); 824 | } 825 | negative = false; 826 | break; 827 | case 93/*']'*/: 828 | if( word == WORD_POS_END ) word = WORD_POS_RESET; 829 | if( parse_context == CONTEXT_IN_ARRAY ) { 830 | //log('_DEBUG_PARSING', "close array, push last element: %d", val.value_type ); 831 | if( val.value_type != VALUE_UNSET ) { 832 | arrayPush(); 833 | } 834 | val.value_type = VALUE_ARRAY; 835 | val.contains = element_array; 836 | { 837 | const old_context = context_stack.pop(); 838 | //log('_DEBUG_PARSING_STACK',"object pop stack (close array)", context_stack.length ); 839 | val.name = old_context.name; 840 | parse_context = old_context.context; 841 | elements = old_context.elements; 842 | element_array = old_context.element_array; 843 | dropContext( old_context ); 844 | } 845 | if( parse_context == CONTEXT_UNKNOWN ) { 846 | completed = true; 847 | } 848 | } 849 | else { 850 | throwError( `bad context ${parse_context}; fault while parsing`, cInt );// fault 851 | } 852 | negative = false; 853 | break; 854 | case 44/*','*/: 855 | if( word == WORD_POS_END ) word = WORD_POS_RESET; // allow collect new keyword 856 | //log('_DEBUG_PARSING', "comma context:", parse_context, val ); 857 | if( parse_context == CONTEXT_IN_ARRAY ) { 858 | if( val.value_type == VALUE_UNSET ) 859 | val.value_type = VALUE_EMPTY; // in an array, elements after a comma should init as undefined... 860 | 861 | //log('_DEBUG_PARSING', "back in array; push item %d", val.value_type ); 862 | arrayPush(); 863 | RESET_VAL(); 864 | // undefined allows [,,,] to be 4 values and [1,2,3,] to be 4 values with an undefined at end. 865 | } 866 | else if( parse_context == CONTEXT_OBJECT_FIELD_VALUE ) { 867 | // after an array value, it will have returned to OBJECT_FIELD anyway 868 | //log('_DEBUG_PARSING', "comma after field value, push field to object: %s", val.name ); 869 | parse_context = CONTEXT_OBJECT_FIELD; 870 | if( val.value_type != VALUE_UNSET ) { 871 | objectPush(); 872 | RESET_VAL(); 873 | }else 874 | throwError( "Unexpected comma after object field name", cInt ); 875 | } 876 | else { 877 | status = false; 878 | throwError( "bad context; excessive commas while parsing;", cInt );// fault 879 | } 880 | negative = false; 881 | break; 882 | 883 | default: 884 | if( parse_context == CONTEXT_OBJECT_FIELD ) { 885 | switch( cInt ) { 886 | case 96://'`': 887 | case 34://'"': 888 | case 39://'\'': 889 | if( word == WORD_POS_RESET ) { 890 | if( val.value_type != VALUE_UNSET ) 891 | throwError( "String begin after previous value", cInt ); 892 | const string_status = gatherString(cInt ); 893 | //log('_DEBUG_PARSING', "string gather for object field name :", val.string, string_status ); 894 | if( string_status ) { 895 | val.value_type = VALUE_STRING; 896 | } else { 897 | gatheringStringFirstChar = cInt; 898 | gatheringString = true; 899 | } 900 | } else { 901 | throwError( "fault while parsing; quote not at start of field name", cInt ); 902 | } 903 | 904 | break; 905 | case 10://'\n': 906 | pos.line++; 907 | pos.col = 1; 908 | // fall through to normal space handling - just updated line/col position 909 | case 13://'\r': 910 | case 32://' ': 911 | case 160:// : 912 | case 9://'\t': 913 | case 0xFEFF: // ZWNBS is WS though 914 | if( word == WORD_POS_END ) { // allow collect new keyword 915 | word = WORD_POS_RESET; 916 | } 917 | else if( word == WORD_POS_FIELD ) { 918 | word = WORD_POS_AFTER_FIELD; 919 | } 920 | // skip whitespace 921 | break; 922 | default: 923 | if( word == WORD_POS_AFTER_FIELD ) { 924 | status = false; 925 | throwError( "fault while parsing; character unexpected", cInt ); 926 | } 927 | if( word == WORD_POS_RESET ) word = WORD_POS_FIELD; 928 | val.string += str; 929 | break; // default 930 | } 931 | } 932 | else switch( cInt ) { 933 | case 96://'`': 934 | case 34://'"': 935 | case 39: {//'\'': 936 | if( val.value_type === VALUE_UNSET ) { 937 | const string_status = gatherString( cInt ); 938 | //log('_DEBUG_PARSING', "string gather for object field value :", val.string, string_status, completed, input.n, buf.length ); 939 | if( string_status ) { 940 | val.value_type = VALUE_STRING; 941 | word = WORD_POS_END; 942 | } else { 943 | gatheringStringFirstChar = cInt; 944 | gatheringString = true; 945 | } 946 | }else throwError( "String unexpected", cInt ); 947 | break; 948 | } 949 | case 10://'\n': 950 | pos.line++; 951 | pos.col = 1; 952 | // Fallthrough 953 | case 32://' ': 954 | case 160://   955 | case 9://'\t': 956 | case 13://'\r': 957 | case 0xFEFF://'\uFEFF': 958 | if( word == WORD_POS_END ) { 959 | word = WORD_POS_RESET; 960 | if( parse_context == CONTEXT_UNKNOWN ) { 961 | completed = true; 962 | } 963 | break; 964 | } 965 | if( word !== WORD_POS_RESET ) { // breaking in the middle of gathering a keyword. 966 | status = false; 967 | throwError( "fault parsing whitespace", cInt ); 968 | } 969 | break; 970 | //---------------------------------------------------------- 971 | // catch characters for true/false/null/undefined which are values outside of quotes 972 | case 116://'t': 973 | if( word == WORD_POS_RESET ) word = WORD_POS_TRUE_1; 974 | else if( word == WORD_POS_INFINITY_6 ) word = WORD_POS_INFINITY_7; 975 | else { status = false; throwError( "fault parsing", cInt ); }// fault 976 | break; 977 | case 114://'r': 978 | if( word == WORD_POS_TRUE_1 ) word = WORD_POS_TRUE_2; 979 | else { status = false; throwError( "fault parsing", cInt ); }// fault 980 | break; 981 | case 117://'u': 982 | if( word == WORD_POS_TRUE_2 ) word = WORD_POS_TRUE_3; 983 | else if( word == WORD_POS_NULL_1 ) word = WORD_POS_NULL_2; 984 | else if( word == WORD_POS_RESET ) word = WORD_POS_UNDEFINED_1; 985 | else { status = false; throwError( "fault parsing", cInt ); }// fault 986 | break; 987 | case 101://'e': 988 | if( word == WORD_POS_TRUE_3 ) { 989 | val.value_type = VALUE_TRUE; 990 | word = WORD_POS_END; 991 | } else if( word == WORD_POS_FALSE_4 ) { 992 | val.value_type = VALUE_FALSE; 993 | word = WORD_POS_END; 994 | } else if( word == WORD_POS_UNDEFINED_3 ) word = WORD_POS_UNDEFINED_4; 995 | else if( word == WORD_POS_UNDEFINED_7 ) word = WORD_POS_UNDEFINED_8; 996 | else { status = false; throwError( "fault parsing", cInt ); }// fault 997 | break; 998 | case 110://'n': 999 | if( word == WORD_POS_RESET ) word = WORD_POS_NULL_1; 1000 | else if( word == WORD_POS_UNDEFINED_1 ) word = WORD_POS_UNDEFINED_2; 1001 | else if( word == WORD_POS_UNDEFINED_6 ) word = WORD_POS_UNDEFINED_7; 1002 | else if( word == WORD_POS_INFINITY_1 ) word = WORD_POS_INFINITY_2; 1003 | else if( word == WORD_POS_INFINITY_4 ) word = WORD_POS_INFINITY_5; 1004 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1005 | break; 1006 | case 100://'d': 1007 | if( word == WORD_POS_UNDEFINED_2 ) word = WORD_POS_UNDEFINED_3; 1008 | else if( word == WORD_POS_UNDEFINED_8 ) { val.value_type=VALUE_UNDEFINED; word = WORD_POS_END; } 1009 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1010 | break; 1011 | case 105://'i': 1012 | if( word == WORD_POS_UNDEFINED_5 ) word = WORD_POS_UNDEFINED_6; 1013 | else if( word == WORD_POS_INFINITY_3 ) word = WORD_POS_INFINITY_4; 1014 | else if( word == WORD_POS_INFINITY_5 ) word = WORD_POS_INFINITY_6; 1015 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1016 | break; 1017 | case 108://'l': 1018 | if( word == WORD_POS_NULL_2 ) word = WORD_POS_NULL_3; 1019 | else if( word == WORD_POS_NULL_3 ) { 1020 | val.value_type = VALUE_NULL; 1021 | word = WORD_POS_END; 1022 | } else if( word == WORD_POS_FALSE_2 ) word = WORD_POS_FALSE_3; 1023 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1024 | break; 1025 | case 102://'f': 1026 | if( word == WORD_POS_RESET ) word = WORD_POS_FALSE_1; 1027 | else if( word == WORD_POS_UNDEFINED_4 ) word = WORD_POS_UNDEFINED_5; 1028 | else if( word == WORD_POS_INFINITY_2 ) word = WORD_POS_INFINITY_3; 1029 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1030 | break; 1031 | case 97://'a': 1032 | if( word == WORD_POS_FALSE_1 ) word = WORD_POS_FALSE_2; 1033 | else if( word == WORD_POS_NAN_1 ) word = WORD_POS_NAN_2; 1034 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1035 | break; 1036 | case 115://'s': 1037 | if( word == WORD_POS_FALSE_3 ) word = WORD_POS_FALSE_4; 1038 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1039 | break; 1040 | case 73://'I': 1041 | if( word == WORD_POS_RESET ) word = WORD_POS_INFINITY_1; 1042 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1043 | break; 1044 | case 78://'N': 1045 | if( word == WORD_POS_RESET ) word = WORD_POS_NAN_1; 1046 | else if( word == WORD_POS_NAN_2 ) { val.value_type = negative ? VALUE_NEG_NAN : VALUE_NAN; negative = false; word = WORD_POS_END; } 1047 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1048 | break; 1049 | case 121://'y': 1050 | if( word == WORD_POS_INFINITY_7 ) { val.value_type = negative ? VALUE_NEG_INFINITY : VALUE_INFINITY; negative = false; word = WORD_POS_END; } 1051 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1052 | break; 1053 | case 45://'-': 1054 | if( word == WORD_POS_RESET ) negative = !negative; 1055 | else { status = false; throwError( "fault parsing", cInt ); }// fault 1056 | break; 1057 | case 43://'+': 1058 | if( word !== WORD_POS_RESET ) { status = false; throwError( "fault parsing", cInt ); }// fault 1059 | break; 1060 | // 1061 | //---------------------------------------------------------- 1062 | default: 1063 | if( ( cInt >= 48/*'0'*/ && cInt <= 57/*'9'*/ ) || ( cInt == 43/*'+'*/ ) || ( cInt == 46/*'.'*/ ) || ( cInt == 45/*'-'*/ ) ) { 1064 | fromHex = false; 1065 | exponent = false; 1066 | exponent_sign = false; 1067 | exponent_digit = false; 1068 | decimal = false; 1069 | val.string = str; 1070 | input.n = n; 1071 | collectNumber(); 1072 | } 1073 | else { 1074 | status = false; 1075 | throwError( "fault parsing", cInt ); 1076 | } 1077 | break; // default 1078 | } 1079 | break; // default of high level switch 1080 | } 1081 | if( completed ) { 1082 | if( word == WORD_POS_END ) { 1083 | word = WORD_POS_RESET; 1084 | } 1085 | break; 1086 | } 1087 | } 1088 | 1089 | if( n == buf.length ) { 1090 | dropBuffer( input ); 1091 | if( gatheringString || gatheringNumber || parse_context == CONTEXT_OBJECT_FIELD ) { 1092 | retval = 0; 1093 | } 1094 | else { 1095 | if( parse_context == CONTEXT_UNKNOWN && ( val.value_type != VALUE_UNSET || result ) ) { 1096 | completed = true; 1097 | retval = 1; 1098 | } 1099 | } 1100 | } 1101 | else { 1102 | // put these back into the stack. 1103 | input.n = n; 1104 | inQueue.unshift( input ); 1105 | retval = 2; // if returning buffers, then obviously there's more in this one. 1106 | } 1107 | if( completed ) 1108 | break; 1109 | } 1110 | 1111 | if( completed && val.value_type != VALUE_UNSET ) { 1112 | switch( val.value_type ) { 1113 | case VALUE_NUMBER: 1114 | result = ((negative?-1:1) * Number( val.string )); 1115 | break; 1116 | case VALUE_STRING: 1117 | result = val.string; 1118 | break; 1119 | case VALUE_TRUE: 1120 | result = true; 1121 | break; 1122 | case VALUE_FALSE: 1123 | result = false; 1124 | break; 1125 | case VALUE_NULL: 1126 | result = null; 1127 | break; 1128 | case VALUE_UNDEFINED: 1129 | result = undefined; 1130 | break; 1131 | case VALUE_NAN: 1132 | result = NaN; 1133 | break; 1134 | case VALUE_NEG_NAN: 1135 | result = -NaN; 1136 | break; 1137 | case VALUE_INFINITY: 1138 | result = Infinity; 1139 | break; 1140 | case VALUE_NEG_INFINITY: 1141 | result = -Infinity; 1142 | break; 1143 | case VALUE_OBJECT: // never happens 1144 | result = val.contains; 1145 | break; 1146 | case VALUE_ARRAY: // never happens 1147 | result = val.contains; 1148 | break; 1149 | } 1150 | negative = false; 1151 | val.string = ''; 1152 | val.value_type = VALUE_UNSET; 1153 | } 1154 | completed = false; 1155 | return retval; 1156 | } 1157 | }; 1158 | }; 1159 | 1160 | 1161 | 1162 | const _parser = [Object.freeze( JSON6.begin() )]; 1163 | let _parse_level = 0; 1164 | JSON6.parse = function( msg, reviver ) { 1165 | //var parser = JSON6.begin(); 1166 | const parse_level = _parse_level++; 1167 | if( _parser.length <= parse_level ) 1168 | _parser.push( Object.freeze( JSON6.begin() ) ); 1169 | const parser = _parser[parse_level]; 1170 | if (typeof msg !== "string") msg = String(msg); 1171 | parser.reset(); 1172 | if( parser._write( msg, true ) > 0 ) { 1173 | const result = parser.value(); 1174 | if (typeof reviver === 'function') (function walk(holder, key) { 1175 | const value = holder[key]; 1176 | if (value && typeof value === 'object') { 1177 | for (const k in value) { 1178 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1179 | const v = walk(value, k); 1180 | if (v !== undefined) { 1181 | value[k] = v; 1182 | } else { 1183 | delete value[k]; 1184 | } 1185 | } 1186 | } 1187 | } 1188 | return reviver.call(holder, key, value); 1189 | }({'': result}, '')); 1190 | _parse_level--; 1191 | return result; 1192 | } else parser.finalError(); 1193 | return undefined; 1194 | }; 1195 | JSON6.stringify = JSON.stringify; 1196 | 1197 | //--------------------------------------------------------------------------- 1198 | // Stringify 1199 | //--------------------------------------------------------------------------- 1200 | 1201 | JSON6.stringifierActive = null; 1202 | JSON6.stringifier = function() { 1203 | const keywords = { ["true"]:true,["false"]:false,["null"]:null,["NaN"]:NaN,["Infinity"]:Infinity,["undefined"]:undefined }; 1204 | let useQuote = '"'; 1205 | 1206 | let ignoreNonEnumerable = false; 1207 | 1208 | return { 1209 | stringify(o,r,s,as) { return stringify(this, o,r,s,as); }, 1210 | setQuote(q) { useQuote = q; }, 1211 | get ignoreNonEnumerable() { return ignoreNonEnumerable; }, 1212 | set ignoreNonEnumerable(val) { ignoreNonEnumerable = val; }, 1213 | }; 1214 | 1215 | function getIdentifier(s) { 1216 | if( "number" === typeof s && !isNaN( s ) ) { 1217 | return ["'",s.toString(),"'"].join(); 1218 | } 1219 | if( !s.length ) return useQuote+useQuote; 1220 | // should check also for if any non ident in string... 1221 | return ( ( s in keywords /* [ "true","false","null","NaN","Infinity","undefined"].find( keyword=>keyword===s )*/ 1222 | || /([0-9-])/.test(s[0]) 1223 | || /((\n|\r|\t)|[ #{}()<>!+\-*/.:,])/.test( s ) )?(useQuote + JSON6.escape(s) +useQuote):s ); 1224 | } 1225 | 1226 | function stringify( stringifier, object, replacer, space, asField ) { 1227 | if( object === undefined ) return "undefined"; 1228 | if( object === null ) return "null"; 1229 | let gap; 1230 | let indent; 1231 | let i; 1232 | const spaceType = typeof space; 1233 | const repType = typeof replacer; 1234 | gap = ""; 1235 | indent = ""; 1236 | const stringifier_ = JSON6.stringifierActive; 1237 | JSON6.stringifierActive = stringifier; 1238 | 1239 | if( !asField ) { 1240 | asField = ""; 1241 | } 1242 | 1243 | 1244 | // If the space parameter is a number, make an indent string containing that 1245 | // many spaces. 1246 | if (spaceType === "number") { 1247 | for (i = 0; i < space; i += 1) { 1248 | indent += " "; 1249 | } 1250 | 1251 | // If the space parameter is a string, it will be used as the indent string. 1252 | } else if (spaceType === "string") { 1253 | indent = space; 1254 | } 1255 | 1256 | // If there is a replacer, it must be a function or an array. 1257 | // Otherwise, throw an error. 1258 | const rep = replacer; 1259 | if( replacer && repType !== "function" 1260 | && ( repType !== "object" 1261 | || typeof replacer.length !== "number" 1262 | )) { 1263 | throw new Error("JSON6.stringify unknown replacer type."); 1264 | } 1265 | 1266 | const r = str( asField, {[asField]:object} ); 1267 | JSON6.stringifierActive = stringifier_; 1268 | //DEBUG_STRINGIFY_OUTPUT && console.trace( "Stringify Result:", r ); 1269 | return r; 1270 | 1271 | 1272 | // from https://github.com/douglascrockford/JSON-js/blob/master/json2.js#L181 1273 | function str(key, holder) { 1274 | // Produce a string from holder[key]. 1275 | let i; // The loop counter. 1276 | let k; // The member key. 1277 | let v; // The member value. 1278 | let length; 1279 | const mind = gap; 1280 | let partial; 1281 | let value = holder[key]; 1282 | if( "string" === typeof value ) value = getIdentifier( value ); 1283 | 1284 | if( value !== undefined 1285 | && value !== null 1286 | && typeof value === "object" 1287 | && typeof toJSOX === "function" 1288 | ) { 1289 | // is encoding? 1290 | gap += indent; 1291 | gap = mind; 1292 | } 1293 | // If we were called with a replacer function, then call the replacer to 1294 | // obtain a replacement value. 1295 | 1296 | if (typeof rep === "function") { 1297 | value = rep.call(holder, key, value); 1298 | } 1299 | 1300 | // What happens next depends on the value's type. 1301 | switch (typeof value) { 1302 | case "string": 1303 | return value; 1304 | case "number": 1305 | return '' + value;//useQuote+JSOX.escape( value )+useQuote; 1306 | case "boolean": 1307 | return String(value); 1308 | case "object": 1309 | 1310 | //_DEBUG_STRINGIFY && console.log( "ENTERING OBJECT EMISSION WITH:", v ); 1311 | //if( v ) return v; 1312 | 1313 | // Due to a specification blunder in ECMAScript, typeof null is "object", 1314 | // so watch out for that case. 1315 | if (!value) { 1316 | return "null"; 1317 | } 1318 | 1319 | // Make an array to hold the partial results of stringifying this object value. 1320 | 1321 | gap += indent; 1322 | partial = []; 1323 | 1324 | // If the replacer is an array, use it to select the members to be stringified. 1325 | if (rep && typeof rep === "object") { 1326 | length = rep.length; 1327 | //_DEBUG_STRINGIFY && console.log( "Working through replacer" ); 1328 | for (i = 0; i < length; i += 1) { 1329 | if (typeof rep[i] === "string") { 1330 | k = rep[i]; 1331 | v = str(k, value); 1332 | if (v) { 1333 | partial.push(getIdentifier(k) + ( 1334 | (gap) 1335 | ? ": " 1336 | : ":" 1337 | ) + v); 1338 | } 1339 | } 1340 | } 1341 | } else { 1342 | 1343 | // Otherwise, iterate through all of the keys in the object. 1344 | const keys = []; 1345 | //_DEBUG_STRINGIFY && console.log( "is something in something?", k, value ); 1346 | for (k in value) { 1347 | if( ignoreNonEnumerable ) 1348 | if( !Object.prototype.propertyIsEnumerable.call( value, k ) ){ 1349 | //_DEBUG_STRINGIFY && console.log( "skipping non-enuerable?", k ); 1350 | continue; 1351 | } 1352 | 1353 | // sort properties into keys. 1354 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1355 | let n; 1356 | for( n = 0; n < keys.length; n++ ) 1357 | if( keys[n] > k ) { 1358 | keys.splice(n,0,k ); 1359 | break; 1360 | } 1361 | if( n === keys.length ) 1362 | keys.push(k); 1363 | } 1364 | } 1365 | //_DEBUG_STRINGIFY && console.log( "Expanding object keys:", v, keys ); 1366 | for(let n = 0; n < keys.length; n++) { 1367 | k = keys[n]; 1368 | if (Object.prototype.hasOwnProperty.call(value, k)) { 1369 | v = str(k, value); 1370 | if (v) { 1371 | partial.push(getIdentifier(k)+ ( 1372 | (gap) 1373 | ? ": " 1374 | : ":" 1375 | ) + v); 1376 | } 1377 | } 1378 | } 1379 | } 1380 | 1381 | // Join all of the member texts together, separated with commas, 1382 | // and wrap them in braces. 1383 | //_DEBUG_STRINGIFY && console.log( "partial:", partial, protoConverter ) 1384 | v = '' + 1385 | ( partial.length === 0 1386 | ? "{}" 1387 | : gap 1388 | ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" 1389 | : "{" + partial.join(",") + "}" 1390 | ); 1391 | gap = mind; 1392 | //_DEBUG_STRINGIFY && console.log(" Resulting phrase from this part is:", v ); 1393 | return v; 1394 | } 1395 | } 1396 | } 1397 | }; 1398 | 1399 | JSON6.stringify = function( object, replacer, space ) { 1400 | const stringifier = JSON6.stringifier(); 1401 | return stringifier.stringify( object, replacer, space ); 1402 | }; 1403 | 1404 | JSON6.version = version; 1405 | -------------------------------------------------------------------------------- /lib/require.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // require.js 3 | // Node.js only: adds a require() hook for .json6 files, just like the native 4 | // hook for .json files. 5 | // 6 | // Usage: 7 | // require('json6/require'); 8 | // require('./foo'); // will check foo.json5 after foo.js, foo.json, etc. 9 | // require('./bar.json6'); 10 | 11 | const FS = require('fs'); 12 | const JSON6 = require('./json6'); 13 | 14 | // Modeled off of (v0.6.18 link; check latest too): 15 | // https://github.com/joyent/node/blob/v0.6.18/lib/module.js#L468-L472 16 | require.extensions['.json6'] = function (module, filename) { 17 | const content = FS.readFileSync(filename, 'utf8'); 18 | module.exports = JSON6.parse(content); 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-6", 3 | "version": "1.1.4", 4 | "description": "JSON for the ES6 era.", 5 | "keywords": [ 6 | "json", 7 | "es6" 8 | ], 9 | "author": "d3x0r ", 10 | "contributors": [], 11 | "module": "dist/index.mjs", 12 | "main": "lib/json6.js", 13 | "bin": "lib/cli.js", 14 | "browser": "dist/index.js", 15 | "files": [ 16 | "dist/", 17 | "lib/" 18 | ], 19 | "dependencies": {}, 20 | "devDependencies": { 21 | "@babel/preset-env": "latest", 22 | "@rollup/plugin-babel": "latest", 23 | "@rollup/plugin-commonjs": "latest", 24 | "@rollup/plugin-node-resolve": "^7", 25 | "@rollup/plugin-strip": "latest", 26 | "acorn": "latest", 27 | "chai": "latest", 28 | "core-js": "latest", 29 | "eslint": "latest", 30 | "husky": "latest", 31 | "lint-staged": "latest", 32 | "mocha": "latest", 33 | "nyc": "latest", 34 | "rollup": "latest", 35 | "rollup-plugin-terser": "latest" 36 | }, 37 | "nyc": { 38 | "statements": 100, 39 | "branches": 100, 40 | "functions": 100, 41 | "lines": 100, 42 | "ignore-class-method": [ 43 | "log" 44 | ], 45 | "reporter": [ 46 | "lcov", 47 | "text" 48 | ] 49 | }, 50 | "scripts": { 51 | "prepack": "npm run test-lite", 52 | "lint": "eslint --cache .", 53 | "build": "node ./lib/cli.js -c package.json6 && rollup -c", 54 | "mocha-lite": "mocha --require chai/register-expect --experimental-loader=./lib/import.mjs --require ./lib/require.js ", 55 | "mocha": "mocha --require chai/register-expect --require ./lib/require.js --experimental-loader=./lib/import.mjs --recursive", 56 | "nyc-lite": "nyc npm run mocha-lite", 57 | "nyc": "nyc npm run mocha", 58 | "test-lite": "npm run lint && npm run build && npm run nyc-lite", 59 | "test": "npm run lint && npm run build && npm run nyc" 60 | }, 61 | "homepage": "http://npmjs.org/package/json-6/", 62 | "engines": { 63 | "node": ">=10.0.0" 64 | }, 65 | "bugs": "https://github.com/d3x0r/json6/issues", 66 | "license": "MIT", 67 | "repository": { 68 | "type": "git", 69 | "url": "https://github.com/d3x0r/json6" 70 | }, 71 | "husky": { 72 | "hooks": { 73 | "pre-push": "npm run test-lite", 74 | "pre-commit": "lint-staged" 75 | } 76 | }, 77 | "lint-staged": { 78 | "*.js": "eslint --cache --fix" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /package.json6: -------------------------------------------------------------------------------- 1 | // source file for package.json 2 | // use npm build to produce normalized package.json file. 3 | 4 | 5 | { 6 | name: "json-6", 7 | version: "1.1.4", 8 | description: "JSON for the ES6 era.", 9 | keywords: [ 10 | "json", 11 | "es6" 12 | ], 13 | author: "d3x0r ", 14 | contributors: [ 15 | ], 16 | module: "dist/index.mjs", 17 | main: "lib/json6.js", 18 | bin: "lib/cli.js", 19 | browser: "dist/index.js", 20 | files: [ 21 | "dist/", 22 | "lib/" 23 | ], 24 | dependencies: {}, 25 | devDependencies: { 26 | '@babel/preset-env': 'latest', 27 | '@rollup/plugin-babel': 'latest', 28 | '@rollup/plugin-commonjs': 'latest', 29 | '@rollup/plugin-node-resolve': '^7', 30 | "@rollup/plugin-strip": 'latest', 31 | acorn:'latest', 32 | chai: 'latest', 33 | 'core-js': 'latest', 34 | eslint: 'latest', 35 | husky: 'latest', 36 | 'lint-staged': 'latest', 37 | mocha:'latest', 38 | nyc:'latest', 39 | rollup: 'latest', 40 | 'rollup-plugin-terser': 'latest', 41 | }, 42 | nyc: { 43 | statements: 100, 44 | branches: 100, 45 | functions: 100, 46 | lines: 100, 47 | ignore-class-method: ['log'], 48 | reporter: [ 49 | 'lcov', 50 | 'text' 51 | ] 52 | }, 53 | scripts: { 54 | prepack: "npm run test-lite", 55 | lint: "eslint --cache .", 56 | build: "node ./lib/cli.js -c package.json6 && rollup -c", 57 | 'mocha-lite': 'mocha --require chai/register-expect --experimental-loader=./lib/import.mjs --require ./lib/require.js ', 58 | mocha: 'mocha --require chai/register-expect --require ./lib/require.js --experimental-loader=./lib/import.mjs --recursive', 59 | 'nyc-lite': 'nyc npm run mocha-lite', 60 | nyc: 'nyc npm run mocha', 61 | 'test-lite': 'npm run lint && npm run build && npm run nyc-lite', 62 | test: 'npm run lint && npm run build && npm run nyc' 63 | }, 64 | homepage: "http://npmjs.org/package/json-6/", 65 | engines: { 66 | node: ">=10.0.0" 67 | }, 68 | bugs: "https://github.com/d3x0r/json6/issues", 69 | license: "MIT", 70 | repository: { 71 | type: "git", 72 | url: "https://github.com/d3x0r/json6" 73 | }, 74 | husky: { 75 | hooks: { 76 | 'pre-push': 'npm run test-lite', 77 | 'pre-commit': 'lint-staged' 78 | } 79 | }, 80 | 'lint-staged': { 81 | '*.js': 'eslint --cache --fix' 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const nodeResolve = require('@rollup/plugin-node-resolve'); 3 | const commonjs = require('@rollup/plugin-commonjs'); 4 | const {babel} = require('@rollup/plugin-babel'); 5 | const strip = require('@rollup/plugin-strip'); 6 | const terser = require('rollup-plugin-terser').terser; 7 | const pkg = require('./package.json'); 8 | 9 | module.exports = [ 10 | // ES5 Non-minified 11 | { 12 | input: 'build/es5.js', 13 | output: { 14 | file: pkg.browser, 15 | format: 'umd', 16 | name: 'JSON5', 17 | }, 18 | plugins: [ 19 | strip({functions: ['log']}), 20 | nodeResolve(), 21 | commonjs(), 22 | babel(), 23 | ], 24 | }, 25 | // ES5 Minified 26 | { 27 | input: 'build/es5.js', 28 | output: { 29 | file: pkg.browser.replace(/\.js$/, '.min.js'), 30 | format: 'umd', 31 | name: 'JSON5', 32 | }, 33 | plugins: [ 34 | strip({functions: ['log']}), 35 | nodeResolve(), 36 | commonjs(), 37 | babel(), 38 | terser(), 39 | ], 40 | }, 41 | // ES6 Modules Non-minified 42 | { 43 | input: 'lib/index.js', 44 | output: { 45 | file: pkg.browser.replace(/\.js$/, '.mjs'), 46 | format: 'esm', 47 | }, 48 | plugins: [ 49 | strip({functions: ['log']}), 50 | nodeResolve(), 51 | commonjs(), 52 | babel(), 53 | ], 54 | }, 55 | // ES6 Modules Minified 56 | { 57 | input: 'lib/index.js', 58 | output: { 59 | file: pkg.browser.replace(/\.js$/, '.min.mjs'), 60 | format: 'esm', 61 | }, 62 | plugins: [ 63 | strip({functions: ['log']}), 64 | nodeResolve(), 65 | commonjs(), 66 | terser(), 67 | babel(), 68 | ], 69 | }, 70 | ]; 71 | -------------------------------------------------------------------------------- /test/1.0.9-require.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require( ".." ); 3 | //require( "../lib/require.js" ); 4 | 5 | describe('Added in 1.0.9 - require(cjs)', function () { 6 | 7 | it('allows using require on extension', function () { 8 | const config = require( "./1.0.9-require.json6" ); 9 | expect( config ).to.deep.equal( { 10 | desc: "configuration file to read", 11 | value:123} 12 | ); 13 | } ); 14 | } ); 15 | -------------------------------------------------------------------------------- /test/1.0.9-require.json6: -------------------------------------------------------------------------------- 1 | { 2 | desc: "configuration file to read", 3 | value:123 4 | } 5 | -------------------------------------------------------------------------------- /test/1.0.9-require.mjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as JSON6 from "../dist/index.mjs"; 3 | //require( "../lib/require.js" ); 4 | 5 | import {default as config} from './1.0.9-require.json6' 6 | 7 | describe('Added in 1.0.9 - require(esm)', function () { 8 | it('extends import operator', function() { 9 | expect( config ).to.deep.equal( { 10 | desc: "configuration file to read", 11 | value:123} 12 | ); 13 | 14 | } ); 15 | 16 | it('allows using require on extension', function () { 17 | return import( "./1.0.9-require.json6" ).then( newConfig=>{ 18 | //console.log( "GOT:", newConfig ); 19 | expect( newConfig.default ).to.deep.equal( { 20 | desc: "configuration file to read", 21 | value:123} 22 | ); 23 | } ); 24 | } ); 25 | } ); 26 | -------------------------------------------------------------------------------- /test/1.0.9-stringify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | describe('JSON6 stringify', function () { 5 | 6 | it('stringifies Infinity', function () { 7 | expect( JSON6.stringify( { d:Infinity } ) ).to.equal( '{d:Infinity}' ); 8 | } ); 9 | 10 | it('stringifies NaN', function () { 11 | expect( JSON6.stringify( { e:NaN } ) ).to.equal( '{e:NaN}' ); 12 | } ); 13 | 14 | it('basically stringifies', function () { 15 | expect( JSON6.stringify( { a:1 16 | , b:"123" 17 | , c:null 18 | , d:Infinity, e:NaN 19 | , f:false 20 | , t:true } ) ) 21 | .to.equal( '{a:1,b:"123",c:null,d:Infinity,e:NaN,f:false,t:true}' ); 22 | } ); 23 | 24 | it('canonically stringifies', function () { 25 | expect( JSON6.stringify( { z:1 26 | , y:"123" 27 | , x:null 28 | , w:Infinity 29 | , v:NaN 30 | , f:false 31 | , '':'' 32 | , get g() { return 0; } 33 | , t:true } ) ) 34 | .to.equal( '{"":"",f:false,g:0,t:true,v:NaN,w:Infinity,x:null,y:"123",z:1}' ); 35 | } ); 36 | 37 | it('can skip non-enumerable', function () { 38 | const stringifier = JSON6.stringifier(); 39 | stringifier.ignoreNonEnumerable = true; 40 | const obj = { z:1 41 | , y:"123" 42 | , x:null 43 | , w:Infinity 44 | , v:NaN 45 | , f:false 46 | , t:true }; 47 | Object.defineProperty( obj, "g", { writable:true, value:true } ); 48 | expect( stringifier.stringify( obj ) ) 49 | .to.equal( '{f:false,t:true,v:NaN,w:Infinity,x:null,y:"123",z:1}' ); 50 | } ); 51 | 52 | 53 | } ); 54 | -------------------------------------------------------------------------------- /test/1.0.9.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | describe('JSON6.forgiving leading `+` ', function () { 5 | it('accepts `+ 8`', function () { 6 | expect( JSON6.parse( '+ 8' ) ).to.equal( 8 ); 7 | } ); 8 | it('accepts `+ Infinity`', function () { 9 | expect( JSON6.parse( '+ Infinity' ) ).to.equal( Infinity ); 10 | } ); 11 | it('throws `123+44`', function () { 12 | expect( function() { JSON6.parse( '123+44' ); } ).to.throw( Error ); 13 | } ); 14 | } ); 15 | -------------------------------------------------------------------------------- /test/1.1.2-bad-require.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require( ".." ); 3 | //require( "../lib/require.js" ); 4 | 5 | describe('Added in 1.1.2(JSON6) - bad require(cjs)', function () { 6 | 7 | it('allows using require on extension', function () { 8 | expect( ()=>{ 9 | const config = require( "./1.1.2-bad-require.json6" ); 10 | expect( config ).to.deep.equal( { 11 | desc: "configuration file to read", 12 | value:123} 13 | ); 14 | } ).to.throw(); 15 | } ); 16 | } ); 17 | -------------------------------------------------------------------------------- /test/1.1.2-bad-require.json6: -------------------------------------------------------------------------------- 1 | { 2 | -------------------------------------------------------------------------------- /test/TestObjectKeys.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | describe('Object keys', function () { 5 | describe('Erring', function () { 6 | it('Unencapsulated hyphenated key with space', function () { 7 | expect(function () { 8 | JSON6.parse( "{ my- key:3}" ); 9 | }).to.throw(Error); 10 | }); 11 | 12 | it('Unencapsulated hyphenated key with spaces', function () { 13 | expect(function () { 14 | JSON6.parse( "{ my - key:3}" ); 15 | }).to.throw(Error); 16 | }); 17 | 18 | it('Unencapsulated hyphenated key with nested object', function () { 19 | expect(function () { 20 | JSON6.parse( "{ my-key { failure:true}:3}" ); 21 | }).to.throw(Error); 22 | }); 23 | 24 | it('Unencapsulated key with nested object', function () { 25 | expect(function () { 26 | JSON6.parse( "{ { my-key:3 } }" ); 27 | }).to.throw(Error); 28 | }); 29 | 30 | it('Unencapsulated key with nested array', function () { 31 | expect(function () { 32 | JSON6.parse( "{ [ my-key:3 } }" ); 33 | }).to.throw(Error); 34 | }); 35 | 36 | it('Unencapsulated key with opening array bracket', function () { 37 | expect(function () { 38 | JSON6.parse( "{ my-key[:3 } }" ); 39 | }).to.throw(Error); 40 | }); 41 | 42 | it('Unencapsulated key with closing array bracket', function () { 43 | expect(function () { 44 | JSON6.parse( "{ my-key]:3 } }" ); 45 | }).to.throw(Error); 46 | }); 47 | 48 | it('Unencapsulated key with opening array bracket and space', function () { 49 | expect(function () { 50 | JSON6.parse( "{ my-key [:3 } }" ); 51 | }).to.throw(Error); 52 | }); 53 | 54 | it('Unencapsulated key with closing array bracket and space', function () { 55 | expect(function () { 56 | JSON6.parse( "{ my-key ]:3 } }" ); 57 | }).to.throw(Error); 58 | }); 59 | 60 | it('Key with quote not at beginning', function () { 61 | expect(function () { 62 | JSON6.parse( "{A': 3}" ); 63 | }).to.throw(Error, /quote not at start of field name/); 64 | }); 65 | }); 66 | 67 | describe('Functional', function () { 68 | it('Parses encapsulated key', function () { 69 | const result = JSON6.parse( "{ 'my - key':3}" ); 70 | 71 | expect(result).to.deep.equal({ 72 | 'my - key': 3 73 | }); 74 | }); 75 | 76 | it('Parses encapsulated key with carriage return', function () { 77 | const result = JSON6.parse( "{ '\\\rmy - key':3}" ); 78 | 79 | expect(result).to.deep.equal({ 80 | 'my - key': 3 81 | }); 82 | }); 83 | 84 | it('Parses key with special characters but no spaces', function () { 85 | const result = JSON6.parse( "{ my-key\\m&m+*|:3}" ); 86 | expect(result).to.deep.equal({ 87 | 'my-key\\m&m+*|' : 3 88 | }); 89 | }); 90 | 91 | it('Parses key with special characters and comment', function () { 92 | const result = JSON6.parse( "{ my-key //test \n :3}" ); 93 | /* 94 | { my-key //test 95 | :3} // valid 96 | */ 97 | expect(result).to.deep.equal({ 98 | 'my-key': 3 99 | }); 100 | }); 101 | 102 | it('Parses key with special characters and multi-line comment', function () { 103 | const result = JSON6.parse( "{ my-key /*test */ :3}" ); 104 | 105 | /* 106 | { my-key /*test * / :3} // valid 107 | */ 108 | expect(result).to.deep.equal({ 109 | 'my-key': 3 110 | }); 111 | }); 112 | 113 | it('Parses key with ZWNBS (ignoring it)', function () { 114 | const result = JSON6.parse( "{A\uFEFF: 3}" ); 115 | 116 | expect(result).to.deep.equal({ 117 | 'A': 3 118 | }); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/benchmarks/fundamentals/stringBench.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const string = "012359599323"; 4 | 5 | describe('String benchmarks', function () { 6 | this.timeout(10000); 7 | it('String index access', function () { 8 | const start = Date.now(); 9 | for( let m = 0; m < 10000; m++ ) 10 | for( let n = 0; n < 100000; n++ ) 11 | if( string[0] === '0' ); 12 | const end = Date.now(); 13 | console.log( "1B in ", end-start ); 14 | }); 15 | it('String charCode access', function () { 16 | const start = Date.now(); 17 | for( let m = 0; m < 10000; m++ ) 18 | for( let n = 0; n < 100000; n++ ) 19 | if( string.charCodeAt(0) === 48 ); 20 | const end = Date.now(); 21 | 22 | console.log( "1B in ", end-start ); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/benchmarks/fundamentals/testNumberConvert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let negative = true; 4 | const val = { string : "1234", negative : false }; 5 | 6 | describe('Number conversions', function () { 7 | this.timeout(10000); 8 | it('Negative number', function () { 9 | const start = Date.now(); 10 | for( let m = 0; m < 5000; m++ ) { 11 | val.negative = m & 1; 12 | for( let n = 0; n < 100000; n++ ) 13 | //negative?-Number( "1234" ):Number("1234"); 14 | val.negative ? -Number(val.string) : Number(val.string); 15 | } 16 | console.log( "took:", Date.now() - start ); 17 | }); 18 | it('Negative number (single negating expression)', function () { 19 | const start = Date.now(); 20 | 21 | for( let m = 0; m < 5000; m++ ) { 22 | val.negative = m & 1; 23 | for( let n = 0; n < 100000; n++ ) 24 | (val.negative ? -1 : 1) * Number(val.string); 25 | // Number("1234") * (negative?-1:1); 26 | } 27 | console.log( "took:", Date.now() - start ); 28 | 29 | }); 30 | it('Negative number (multiplying negative factor)', function () { 31 | const start = Date.now(); 32 | 33 | for( let m = 0; m < 5000; m++ ) { 34 | val.negative = m & 1; 35 | for( let n = 0; n < 100000; n++ ) 36 | Number(val.string) * (val.negative ? -1 : 1); 37 | // Number("1234") * (negative?-1:1); 38 | } 39 | console.log( "took:", Date.now() - start ); 40 | 41 | }); 42 | it('Negative number (changing variable)', function () { 43 | const start = Date.now(); 44 | for( let m = 0; m < 5000; m++ ) { 45 | negative = m & 1; 46 | for( let n = 0; n < 100000; n++ ) 47 | // negative ? -Number( "1234" ) : Number("1234"); 48 | negative ? -Number(val.string) : Number(val.string); 49 | } 50 | console.log( "took:", Date.now() - start ); 51 | 52 | }); 53 | it('Negative number (changing variable and multiplying)', function () { 54 | const start = Date.now(); 55 | 56 | for( let m = 0; m < 5000; m++ ) { 57 | negative = m & 1; 58 | for( let n = 0; n < 100000; n++ ) 59 | Number(val.string) * (negative ? -1 : 1); 60 | // Number("1234") * (negative ? -1 : 1); 61 | } 62 | console.log( "took:", Date.now() - start ); 63 | 64 | }); 65 | it('Negative number (changing variable and multiplying in front)', function () { 66 | const start = Date.now(); 67 | 68 | for( let m = 0; m < 5000; m++ ) { 69 | negative = m & 1; 70 | for( let n = 0; n < 100000; n++ ) 71 | (negative ? -1 : 1) * Number(val.string); 72 | // Number("1234") * (negative ? -1 : 1); 73 | } 74 | console.log( "took:", Date.now() - start ); 75 | 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/benchmarks/json6NumberTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JSON = require( "../../" ); 4 | 5 | describe('Number tests (with benchmarking)', function () { 6 | this.timeout(10000); 7 | it('Negative decimal', function () { 8 | console.log( JSON.parse( "-1234" ) ); 9 | const start = Date.now(); 10 | let result; 11 | for( let m = 0; m < 40; m++ ) 12 | for( let n = 0; n < 100000; n++ ) 13 | result = JSON.parse( "-1234" ); 14 | 15 | console.log( "took:", Date.now() - start ); 16 | expect(result).to.equal(-1234); 17 | }); 18 | 19 | it('Negative decimal (single loop)', function () { 20 | console.log( JSON.parse( "-1234" ) ); 21 | const start = Date.now(); 22 | let result; 23 | for( let n = 0; n < 2000000; n++ ) 24 | result = JSON.parse( "-1234" ); 25 | console.log( "took:", Date.now() - start ); 26 | expect(result).to.equal(-1234); 27 | }); 28 | 29 | it('Decimal', function () { 30 | console.log( JSON.parse( "1234" ) ); 31 | const start = Date.now(); 32 | let result; 33 | for( let m = 0; m < 40; m++ ) 34 | for( let n = 0; n < 100000; n++ ) 35 | result = JSON.parse( "1234" ); 36 | console.log( "took:", Date.now() - start ); 37 | expect(result).to.equal(1234); 38 | }); 39 | 40 | it('Decimal (single loop)', function () { 41 | console.log( JSON.parse( "1234" ) ); 42 | const start = Date.now(); 43 | let result; 44 | for( let n = 0; n < 2000000; n++ ) 45 | result = JSON.parse( "1234" ); 46 | console.log( "took:", Date.now() - start ); 47 | expect(result).to.equal(1234); 48 | }); 49 | 50 | it('Simple object with number', function () { 51 | console.log( JSON.parse( "{a:1234}" ) ); 52 | const start = Date.now(); 53 | let result; 54 | for( let n = 0; n < 2000000; n++ ) 55 | result = JSON.parse( "{a:1234}" ); 56 | console.log( "took:", Date.now() - start ); 57 | expect(result).to.deep.equal({ 58 | a: 1234 59 | }); 60 | }); 61 | 62 | it('Simple object with negative number', function () { 63 | console.log( JSON.parse( "{a:-1234}" ) ); 64 | const start = Date.now(); 65 | let result; 66 | for( let n = 0; n < 2000000; n++ ) 67 | result = JSON.parse( "{a:-1234}" ); 68 | console.log( "took:", Date.now() - start ); 69 | expect(result).to.deep.equal({ 70 | a: -1234 71 | }); 72 | }); 73 | 74 | it('Simple array with number', function () { 75 | console.log( JSON.parse( "[1234]" ) ); 76 | const start = Date.now(); 77 | let result; 78 | for( let n = 0; n < 2000000; n++ ) 79 | result = JSON.parse( "[1234]" ); 80 | console.log( "took:", Date.now() - start ); 81 | expect(result).to.deep.equal([1234]); 82 | }); 83 | 84 | it('Simple array with negative number', function () { 85 | console.log( JSON.parse( "[-1234]" ) ); 86 | const start = Date.now(); 87 | let result; 88 | for( let n = 0; n < 2000000; n++ ) 89 | result = JSON.parse( "[-1234]" ); 90 | console.log( "took:", Date.now() - start ); 91 | expect(result).to.deep.equal([-1234]); 92 | }); 93 | 94 | 95 | it('Simple object with decimal', function () { 96 | console.log( JSON.parse( "{a:0.1234}" ) ); 97 | const start = Date.now(); 98 | let result; 99 | for( let n = 0; n < 1000000; n++ ) 100 | result = JSON.parse( "{a:0.1234}" ); 101 | console.log( "took:", Date.now() - start ); 102 | expect(result).to.deep.equal({a: 0.1234}); 103 | }); 104 | 105 | it('Simple object with negative octal treated as decimal', function () { 106 | console.log( JSON.parse( "{a:-01234}" ) ); 107 | const start = Date.now(); 108 | let result; 109 | for( let n = 0; n < 1009000; n++ ) 110 | result = JSON.parse( "{a:-01234}" ); 111 | console.log( "took:", Date.now() - start ); 112 | expect(result).to.deep.equal({a: -1234}); 113 | }); 114 | 115 | it('Simple array with negative hexadecimal', function () { 116 | console.log( JSON.parse( "[-0x1f9d]" ) ); 117 | const start = Date.now(); 118 | let result; 119 | for( let m = 0; m < 20; m++ ) 120 | for( let n = 0; n < 100000; n++ ) 121 | result = JSON.parse( "[-0x1f9d]" ); 122 | console.log( "took:", Date.now() - start ); 123 | expect(result).to.deep.equal([-0x1f9d]); 124 | }); 125 | 126 | // console.log( "Waiting forever..." ); 127 | // function wait() { setTimeout( wait, 2000 ) } 128 | // wait(); 129 | }); 130 | -------------------------------------------------------------------------------- /test/benchmarks/json6ObjectArrays.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JSON6 = require( "../../" ); 4 | const parse = JSON6.parse; 5 | 6 | // benchmark - needs some work; ended up somewhat divergent. 7 | describe('Objects/Arrays benchmarks', function () { 8 | this.timeout(25000); 9 | it('Benchmark nested objects', function () { 10 | let start = Date.now(); 11 | let result; 12 | for( let n = 0; n < 1000000; n++ ) { 13 | result = parse( "{\"a\":{\"b\":{\"c\":{\"d\":123}}}}" ); 14 | //parse( '"Simple String Value."' ); 15 | } 16 | let end = Date.now(); 17 | console.log( "1m in ", end-start ); 18 | expect(result).to.deep.equal({ 19 | a: {b: {c: {d: 123}}}} 20 | ); 21 | 22 | start = Date.now(); 23 | for( let n = 0; n < 1000000; n++ ) { 24 | result = parse( "[1,[2,[3,[4,5]]]]" ); 25 | //parse( '"Simple String Value."' ); 26 | } 27 | end = Date.now(); 28 | console.log( "1m in ", end-start ); 29 | expect(result).to.deep.equal([1,[2,[3,[4,5]]]]); 30 | 31 | 32 | // var translations = ["{\"a\":{\"b\":{\"c\":{\"d\":123}}}}","{\"a\":{\"b\":{\"c\":{\"d\":123}}}}","{\"a\":{\"b\":{\"c\":{\"d\":123}}}}","{\"a\":{\"b\":{\"c\":{\"d\":123}}}}"]; 33 | // var ntrans = 0; 34 | 35 | start = end; 36 | for( let n = 0; n < 5000000; n++ ) { 37 | result = JSON.parse( "{\"a\":{\"b\":{\"c\":{\"d\":123}}}}" ); 38 | //JSON.parse( translations[ntrans] ); 39 | //ntrans = (ntrans+1)&3; 40 | } 41 | end = Date.now(); 42 | console.log( "1m in ", end-start ); 43 | expect(result).to.deep.equal({ 44 | a: {b: {c: {d: 123}}} 45 | }); 46 | }); 47 | it('Benchmark nested array', function () { 48 | const start = Date.now(); 49 | let result; 50 | for( let n = 0; n < 5000000; n++ ) { 51 | result = JSON.parse( "[1,[2,[3,[4,5]]]]" ); 52 | //parse( '"Simple String Value."' ); 53 | } 54 | const end = Date.now(); 55 | console.log( "1m in ", end-start ); 56 | expect(result).to.deep.equal( 57 | [1, [2, [3, [4, 5]]]] 58 | ); 59 | }); 60 | // benchmark - needs some work; ended up somewhat divergent. 61 | it('Benchmark nested object with numeric keys', function () { 62 | const varObjects = []; 63 | for( let n = 0; n < 100000; n++ ) { 64 | varObjects.push( 65 | '{"a' + n + '":{"b' + n + '":{"c' + n + '":{"d' + n + '":123}}}}' 66 | ); 67 | } 68 | 69 | /* 70 | var varStrings = []; 71 | for( var n = 0; n < 100000; n++ ) { 72 | varStrings.push( `"SImple STring Value ${n}"` ); 73 | } 74 | 75 | var varNumbers = []; 76 | for( var n = 0; n < 100000; n++ ) { 77 | varNumbers.push( `${n}` ); 78 | } 79 | */ 80 | 81 | 82 | let start = Date.now(); 83 | for( let n = 0; n < 500000; n++ ) { 84 | parse( varObjects[n % 100000] ); 85 | // parse( varStrings[n%100000] ); 86 | // parse( varNumbers[n%100000] ); 87 | // parse( "{\"a\":{\"b\":{\"c\":{\"d\":123}}}}" ); 88 | // parse( '"Simple String value"' ); 89 | // parse( '123456789' ); 90 | } 91 | 92 | let end = Date.now(); 93 | console.log( "1m in ", end-start ); 94 | 95 | 96 | start = end; 97 | for( let n = 0; n < 500000; n++ ) { 98 | JSON.parse( varObjects[n % 100000] ); 99 | // JSON.parse( varStrings[n%100000] ); 100 | // JSON.parse( varNumbers[n%100000] ); 101 | /* JSON.parse( "{\"a\":{\"b\":{\"c\":{\"d\":123}}}}" );*/ 102 | // JSON.parse( '"Simple String value"' ); 103 | // JSON.parse( '123456789' ); 104 | } 105 | end = Date.now(); 106 | console.log( "1m in ", end - start ); 107 | expect(true).to.be.true; 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/benchmarks/json6StringTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // var sack = require( "../../" ); 4 | const JSON6 = require( "../../" ); // sack.JSON6; 5 | 6 | /* 7 | var parser = JSON6.begin( function (data) { 8 | console.log( "Test 123456 = ", data ); 9 | } ); 10 | */ 11 | // parser.write( "\'\\x31\\062\\u{33}\\u003456\'" ) 12 | // var string = "\'\\x31\\062\\u{33}\\u003456\'"; 13 | // for( var n = 0; n < string.length; n++ ) 14 | // parser.write( string[n] ); 15 | 16 | describe('String tests', function () { 17 | this.timeout(7000); 18 | it('Simple string', function () { 19 | console.log( "Output1:", JSON6.parse( '"Simple String value"' ) ); 20 | let start = Date.now(); 21 | let result; 22 | for( let n = 0; n < 1000000; n++ ) { 23 | result = JSON6.parse( '"Simple String value"' ); 24 | } 25 | let end = Date.now(); 26 | console.log( "1m in ", end - start ); 27 | expect(result).to.equal('Simple String value'); 28 | 29 | 30 | start = Date.now(); 31 | for( let n = 0; n < 1000000; n++ ) { 32 | result = JSON.parse( '"Simple String value"' ); 33 | } 34 | end = Date.now(); 35 | console.log( "1m in ", end - start ); 36 | expect(result).to.equal('Simple String value'); 37 | }); 38 | it('String with whitespace escapes', function () { 39 | console.log( "Output2:", JSON6.parse( '"Si\\t \\r\\n lue"' ) ); 40 | let start = Date.now(); 41 | for( let n = 0; n < 1000000; n++ ) { 42 | JSON6.parse( '"Si\\t \\r\\n lue"' ); 43 | } 44 | 45 | let end = Date.now(); 46 | console.log( "1m in ", end - start ); 47 | 48 | start = Date.now(); 49 | for( let n = 0; n < 1000000; n++ ) { 50 | JSON.parse( '"Si\\t \\r\\n lue"' ); 51 | } 52 | 53 | end = Date.now(); 54 | console.log( "1m in ", end-start ); 55 | }); 56 | }); 57 | 58 | // console.log( "Waiting forever..." ); 59 | // function wait() { setTimeout( wait, 2000 ) } 60 | // wait(); 61 | -------------------------------------------------------------------------------- /test/benchmarks/json6Test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JSON6 = require( "../../" ); 4 | 5 | describe('Benchmarking', function () { 6 | this.timeout(20000); 7 | it('Benchmarking', function () { 8 | const start = Date.now(); 9 | let result1, result2, result3; 10 | for( let n = 0; n < 1000000; n++ ) { 11 | result1 = JSON6.parse( "{\"a\":{\"b\":{\"c\":{\"d\":123}}}}" ); 12 | result2 = JSON6.parse( '"Simple String value"' ); 13 | result3 = JSON6.parse( '123456789' ); 14 | } 15 | 16 | const end = Date.now(); 17 | console.log( "1m in ", end - start ); 18 | expect(result1).to.deep.equal({ 19 | a: {b: {c: {d: 123}}} 20 | }); 21 | expect(result2).to.equal('Simple String value'); 22 | expect(result3).to.equal(123456789); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/escape.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | describe('JSON6.escape', function () { 5 | it('Escapes', function () { 6 | const str = JSON6.escape('a"b\\c`d\'e'); 7 | expect(str).to.equal('a\\"b\\\\c\\`d\\\'e'); 8 | }); 9 | it('Handles empty string', function () { 10 | const str = JSON6.escape(''); 11 | expect(str).to.equal(''); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/json6BadTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | const parse = JSON6.parse; 5 | 6 | describe('Bad tests', function () { 7 | 8 | it('space error', function () { 9 | expect(function () { 10 | parse( "tr " ); 11 | }).to.throw(Error); 12 | 13 | expect(function () { 14 | parse( "[tr ]" ); 15 | }).to.throw(Error); 16 | expect(function () { 17 | parse( "{a:tr }" ); 18 | }).to.throw(Error); 19 | } ); 20 | 21 | it('Non-matching keyword (true)', function () { 22 | expect(function () { 23 | parse( "tt" ); 24 | }).to.throw(Error); 25 | 26 | expect(function () { 27 | parse( "trr" ); 28 | }).to.throw(Error); 29 | 30 | expect(function () { 31 | parse( "truu" ); 32 | }).to.throw(Error); 33 | 34 | expect(function () { 35 | parse( "truee" ); 36 | }).to.throw(Error); 37 | }); 38 | 39 | it('Non-matching keyword (false)', function () { 40 | expect(function () { 41 | parse( "ff" ); 42 | }).to.throw(Error); 43 | 44 | expect(function () { 45 | parse( "faa" ); 46 | }).to.throw(Error); 47 | 48 | expect(function () { 49 | parse( "fall" ); 50 | }).to.throw(Error); 51 | 52 | expect(function () { 53 | parse( "falss" ); 54 | }).to.throw(Error); 55 | 56 | expect(function () { 57 | parse( "falsee" ); 58 | }).to.throw(Error); 59 | }); 60 | 61 | it('Non-matching keyword (null)', function () { 62 | expect(function () { 63 | parse( "nn" ); 64 | }).to.throw(Error); 65 | 66 | expect(function () { 67 | parse( "nuu" ); 68 | }).to.throw(Error); 69 | 70 | expect(function () { 71 | parse( "nulll" ); 72 | }).to.throw(Error); 73 | }); 74 | 75 | it('Non-matching keyword (undefined)', function () { 76 | expect(function () { 77 | parse( "uu" ); 78 | }).to.throw(Error); 79 | 80 | expect(function () { 81 | parse( "unn" ); 82 | }).to.throw(Error); 83 | 84 | expect(function () { 85 | parse( "undd" ); 86 | }).to.throw(Error); 87 | 88 | expect(function () { 89 | parse( "undee" ); 90 | }).to.throw(Error); 91 | 92 | expect(function () { 93 | parse( "undeff" ); 94 | }).to.throw(Error); 95 | 96 | expect(function () { 97 | parse( "undefii" ); 98 | }).to.throw(Error); 99 | 100 | expect(function () { 101 | parse( "undefinn" ); 102 | }).to.throw(Error); 103 | 104 | expect(function () { 105 | parse( "undefinee" ); 106 | }).to.throw(Error); 107 | 108 | expect(function () { 109 | parse( "undefinedd" ); 110 | }).to.throw(Error); 111 | }); 112 | 113 | it('Non-matching keyword (NaN)', function () { 114 | expect(function () { 115 | parse( "NN" ); 116 | }).to.throw(Error); 117 | 118 | expect(function () { 119 | parse( "Naa" ); 120 | }).to.throw(Error); 121 | 122 | expect(function () { 123 | parse( "NaNN" ); 124 | }).to.throw(Error); 125 | }); 126 | 127 | it('Non-matching keyword (Infinity)', function () { 128 | expect(function () { 129 | parse( "II" ); 130 | }).to.throw(Error); 131 | 132 | expect(function () { 133 | parse( "Infinityy" ); 134 | }).to.throw(Error); 135 | 136 | expect(function () { 137 | parse( "-Infinity-" ); 138 | }).to.throw(Error); 139 | }); 140 | 141 | it('Unquoted space in identifier', function () { 142 | expect(function () { 143 | parse( "{ a b:1 }" ); 144 | }).to.throw(Error); 145 | }); 146 | 147 | it('Missing colon?', function () { 148 | expect(function () { 149 | parse( "{ a[3], b:1 }" ); 150 | }).to.throw(Error); 151 | }); 152 | 153 | it('Missing colon?', function () { 154 | expect(function () { 155 | parse( "{ a{c:3}, b:1 }" ); 156 | }).to.throw(Error); 157 | }); 158 | 159 | it('String unquoted?', function () { 160 | expect(function () { 161 | parse( "{ a : no quote }" ); 162 | }).to.throw(Error); 163 | }); 164 | 165 | it('String unquoted?', function () { 166 | expect(function () { 167 | parse( "a" ); 168 | }).to.throw(Error); 169 | }); 170 | 171 | it('Non-BMP unquoted String', function () { 172 | expect(function () { 173 | parse( "\u{10FFFF}" ); 174 | }).to.throw(Error); 175 | }); 176 | 177 | it('Throws with colon in array', function () { 178 | expect(function () { 179 | parse( "[:]" ); 180 | }).to.throw(Error); 181 | }); 182 | 183 | it('Throws with colon outside objects', function () { 184 | expect(function () { 185 | parse( ":" ); 186 | }).to.throw(Error); 187 | }); 188 | 189 | it('Throws with comma outside objects', function () { 190 | expect(function () { 191 | parse( "," ); 192 | }).to.throw(Error, /excessive commas/); 193 | }); 194 | 195 | it('Throws with curly bracket outside objects', function () { 196 | expect(function () { 197 | parse( "}" ); 198 | }).to.throw(Error); 199 | }); 200 | 201 | it('Out of place ZWNBS (in keyword state)', function () { 202 | expect(function () { 203 | JSON6.parse( "tru\uFEFF" ); 204 | }).to.throw(Error, /fault parsing whitespace/); 205 | }); 206 | 207 | it('Array after string?', function () { 208 | expect(function () { 209 | parse( "{ a : 'no quote' [1] }" ); 210 | }).to.throw(Error); 211 | }); 212 | 213 | it('comma after object field and : ', function () { 214 | expect(function () { 215 | parse( "{a:,}" ); 216 | }).to.throw(Error); 217 | }); 218 | 219 | it('object close after object field and : ', function () { 220 | expect(function () { 221 | parse( "{a:}" ); 222 | }).to.throw(Error); 223 | }); 224 | 225 | it('bad hex escape : ', function () { 226 | expect(function () { 227 | parse( "'\\x1Z'" ); 228 | }).to.throw(Error); 229 | }); 230 | 231 | it('bad unicode escape : ', function () { 232 | expect(function () { 233 | parse( "'\\u01Zz'" ); 234 | }).to.throw(Error); 235 | }); 236 | 237 | 238 | it('throws with quoted field name after no comma : ', function () { 239 | expect(function () { 240 | parse( '{ "a": { "a": 5 } "abc": { "a": 5 } }' ); 241 | }).to.throw(Error,/String unexpected/); 242 | }); 243 | 244 | it('throws with unquoted field name after no comma: ', function () { 245 | expect(function () { 246 | parse( '{ "a": { "a": 5 } abc: { "a": 5 } }' ); 247 | }).to.throw(Error,/fault parsing 'a' unexpected at/); 248 | }); 249 | 250 | 251 | 252 | }); 253 | -------------------------------------------------------------------------------- /test/json6StreamTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | describe('Stream testing', function () { 5 | it('Receives various values via `write`', function () { 6 | let results = []; 7 | const parser = JSON6.begin(function (obj) { 8 | //console.log( "Got value:", typeof obj, ":", obj ); 9 | results.push(obj); 10 | }); 11 | 12 | parser.write( '"This ' ); 13 | parser.write( 'is a Test"' ); 14 | 15 | parser.write( '[1234,12'); 16 | parser.write( '34,1234]'); 17 | 18 | parser.write( '[123,4'); 19 | parser.write( '56,78'); 20 | parser.write( '9,"abc","de'); 21 | parser.write( 'f","ghi"]'); 22 | 23 | 24 | parser.write( 'true false null undefined NaN Infinity' ); 25 | 26 | parser.write( "1 " ); 27 | parser.write( "123" ); 28 | parser.write( '"1"' ); 29 | 30 | parser.write( '{ a:12' ); 31 | parser.write( '34 }' ); 32 | 33 | parser.write( '{ long'); 34 | parser.write( 'key:1234 }' ); 35 | 36 | parser.write( '{ a:1234 }' ); 37 | //console.log( "4 objects..." ); 38 | parser.write( '{ a:1234 }{ b:34 }{c:1}{d:123}' ); 39 | //console.log( "got 4 objects?" ); 40 | 41 | expect(results).to.deep.equal([ 42 | 'This is a Test', 43 | [1234, 1234, 1234], 44 | [123, 456, 789, 'abc', 'def', 'ghi'], 45 | true, false, null, undefined, NaN, Infinity, 46 | 1, 47 | 123, 48 | '1', 49 | { a: 1234 }, 50 | { longkey: 1234 }, 51 | { a: 1234 }, 52 | { a: 1234 }, 53 | { b: 34 }, 54 | {c: 1}, 55 | {d: 123} 56 | ]); 57 | 58 | expect(function () { 59 | parser.write( 'truefalse' ); 60 | }).to.throw(Error); 61 | 62 | results = []; 63 | 64 | parser.reset(); 65 | parser.write( '1_234 0x55_33_22_11 0x1234 ' ); 66 | console.log( "RESULTS:", results ); 67 | expect(results).to.deep.equal([ 68 | 1234, 69 | 1429414417, 70 | 4660 71 | ]); 72 | 73 | parser.write( '123'); 74 | parser.write(); 75 | 76 | parser.write( '{a:123'); 77 | 78 | expect( function() { 79 | parser.write(); 80 | } ) .to.throw(Error ); 81 | 82 | parser.reset(); 83 | 84 | parser.write( '{a:"String'); 85 | parser.write( 'split Buffer"}' ); 86 | 87 | parser.write( '"String '); 88 | parser.write( 'coverage'); 89 | parser.write( ' test"' ); 90 | 91 | parser.write( '1' ); 92 | parser.write( '2' ); 93 | parser.write( '3' ); 94 | parser.write( '4' ); 95 | parser.write( '5' ); 96 | parser.write( ' ' ); 97 | 98 | 99 | // this is a test to trigger coverage. 100 | results = []; 101 | expect( function() { 102 | parser.write( '{ this is an error' ); 103 | } ).to.throw( Error ); 104 | 105 | expect( function() { 106 | parser.write( '} 0 ' ); 107 | } ).to.throw( Error ); 108 | 109 | parser.reset( ); 110 | parser.write( '"OK"' ); 111 | expect(results).to.deep.equal(["OK"]); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/json6Test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | const parse = JSON6.parse; 4 | 5 | //console.log( "Stringify Test:", vfs.JSON.stringify( { a:123 } ) ); 6 | 7 | //var x = '[' + JSON.stringify( new Date() ) + ']'; 8 | //console.log( "Date Output is:", x, JSON.stringify( new Date() ) ); 9 | 10 | describe('Basic parsing', function () { 11 | describe('Whitespace', function () { 12 | it('accepts space before colon', function () { 13 | const o = parse( "{test : 0 }" ); 14 | //console.log( "123 is", o, typeof o ); 15 | expect(o).to.deep.equal( {test:0} ); 16 | }); 17 | it('accepts space before colon', function () { 18 | const o = parse( "{ test : 0 }" ); 19 | //console.log( "obj is", o, typeof o ); 20 | expect(o).to.deep.equal( {test:0} ); 21 | }); 22 | }); 23 | describe('Numbers', function () { 24 | it('Simple decimal', function () { 25 | const o = parse( "123" ); 26 | //console.log( "123 is", o, typeof o ); 27 | expect(o).to.equal(123); 28 | }); 29 | it('Decimal with bad character', function () { 30 | expect(function () { 31 | parse( "12\u{10FFFF}" ); 32 | }).to.throw(Error, /fault while parsing number/); 33 | }); 34 | it('Decimal with separators', function () { 35 | const o = parse( "123_456_789" ); 36 | //console.log( "123_456_789 is", o, typeof o ); 37 | expect(o).to.equal(123456789); 38 | }); 39 | it('Leading plain zero octals treated as decimals', function () { 40 | const o = parse( "0123" ); 41 | const withinArr = parse( "[0123]" ); 42 | const withinObj = parse( "{a: 0123}" ); 43 | const negO = parse( "-0123" ); 44 | const negWithinArr = parse( "[-0123]" ); 45 | const negWithinObj = parse( "{a: -0123}" ); 46 | //console.log( "0123 is", o, typeof o ); 47 | expect(o).to.equal(123); 48 | expect(withinArr).to.deep.equal([123]); 49 | expect(withinObj).to.deep.equal({a: 123}); 50 | expect(negO).to.equal(-123); 51 | expect(negWithinArr).to.deep.equal([-123]); 52 | expect(negWithinObj).to.deep.equal({a: -123}); 53 | }); 54 | 55 | it('Leading zero-o octals', function () { 56 | const o = parse( "0o123" ); 57 | //console.log( "0o123 is", o, typeof o ); 58 | expect(o).to.equal(83); 59 | }); 60 | it('Hexadecimal', function () { 61 | const o = parse( "0x123" ); 62 | //console.log( "0x123 is", o, typeof o ); 63 | expect(o).to.equal(291); 64 | }); 65 | it('Binary', function () { 66 | const o = parse( "0b1010101" ); 67 | //console.log( "0b1010101 is", o, typeof o ); 68 | expect(o).to.equal(85); 69 | }); 70 | }); 71 | describe('Special numbers', function () { 72 | it('NaN', function () { 73 | const o = parse( "NaN" ); 74 | //console.log( "o is", o, typeof o ); 75 | expect(o).to.be.NaN; 76 | }); 77 | it('-NaN', function () { 78 | const o = parse( "-NaN" ); 79 | //console.log( "o is", o, typeof o ); 80 | expect(o).to.be.NaN; 81 | }); 82 | it('Infinity', function () { 83 | const o = parse( "Infinity" ); 84 | //console.log( "o is", o, typeof o ); 85 | expect(o).to.equal(Infinity); 86 | }); 87 | it('-Infinity', function () { 88 | const o = parse( "-Infinity" ); 89 | //console.log( "o is", o, typeof o ); 90 | expect(o).to.equal(-Infinity); 91 | }); 92 | }); 93 | describe('Strings', function () { 94 | it('String as number', function () { 95 | const o = parse( "\"123\"" ); 96 | expect(o).to.equal('123'); 97 | }); 98 | it('String with non-BMP characters', function () { 99 | const o = parse( "\"\u{10FFFF}\"" ); 100 | expect(o).to.equal('\u{10FFFF}'); 101 | }); 102 | it('String standard whitespace escape characters', function () { 103 | const o = parse( "\"\\n\\r\\f\\t\"" ); 104 | expect(o).to.equal('\n\r\f\t'); 105 | }); 106 | it('String standard whitespace escape characters', function () { 107 | const o = parse( "\"\\xFF\"" ); 108 | expect(o).to.equal('\xFF'); 109 | }); 110 | 111 | it('String standard whitespace escape characters', function () { 112 | const o = parse( "\"\\u01AF\"" ); 113 | expect(o).to.equal('\u01AF'); 114 | }); 115 | }); 116 | describe('Comments', function () { 117 | it('Should throw with invalid comment', function () { 118 | expect(function () { 119 | parse( "/a" ); 120 | }).to.throw(Error); 121 | }); 122 | it('Should throw with incomplete comment (single slash)', function () { 123 | expect(function () { 124 | parse( "/" ); 125 | }).to.throw(Error); 126 | }); 127 | it('Should throw with incomplete comment (closing asterisk)', function () { 128 | expect(function () { 129 | parse( "/* *" ); 130 | }).to.throw(Error); 131 | }); 132 | it('Should not err (will warn) with comment begun at end', function () { 133 | const o = parse( "//" ); 134 | //console.log( "o is", o, typeof o ); 135 | expect(o).to.equal(undefined); 136 | }); 137 | it('Should throw with incomplete comment with 2 asterisks', function () { 138 | expect(function () { 139 | parse( "/**a" ); 140 | }).to.throw(Error); 141 | }); 142 | it('Should throw with incomplete comment with 3 asterisks', function () { 143 | expect(function () { 144 | parse( "/***" ); 145 | }).to.throw(Error); 146 | }); 147 | it('Should handle comment', function () { 148 | const o = parse( "/**/" ); 149 | //console.log( "o is", o, typeof o ); 150 | expect(o).to.equal(undefined); 151 | }); 152 | }); 153 | describe('Other', function () { 154 | it('null', function () { 155 | const o = parse( "null" ); 156 | //console.log( "o is", o, typeof o ); 157 | expect(o).to.be.null; 158 | }); 159 | it('null as `null`', function () { 160 | const o = parse( null ); 161 | //console.log( "o is", o, typeof o ); 162 | expect(o).to.be.null; 163 | }); 164 | it('true', function () { 165 | const o = parse( "true" ); 166 | //console.log( "o is", o, typeof o ); 167 | expect(o).to.be.true; 168 | }); 169 | it('false', function () { 170 | const o = parse( "false" ); 171 | //console.log( "o is", o, typeof o ); 172 | expect(o).to.be.false; 173 | }); 174 | it('undefined', function () { 175 | const o = parse( "undefined" ); 176 | //console.log( "o is", o, typeof o ); 177 | expect(o).to.be.undefined; 178 | }); 179 | }); 180 | describe('Objects', function () { 181 | describe('Keys', function () { 182 | it('Double-quoted key', function () { 183 | const o = parse( "{\"a\":123}" ); 184 | //console.log( "o is", o ); 185 | expect(o).to.deep.equal({ a: 123 }); 186 | }); 187 | it('empty object', function () { 188 | const o = parse( "{}" ); 189 | expect(o).to.deep.equal({}); 190 | }); 191 | it('Back-tick quoted key', function () { 192 | const o = parse( "{`a`:123}" ); 193 | //console.log( "o is", o ); 194 | expect(o).to.deep.equal({ a: 123 }); 195 | }); 196 | it('Carriage return within key', function () { 197 | const o = parse( "{\ra:123}" ); 198 | //console.log( "o is", o ); 199 | expect(o).to.deep.equal({ a: 123 }); 200 | }); 201 | it('Newline within key', function () { 202 | const o = parse( "{\na:123}" ); 203 | //console.log( "o is", o ); 204 | expect(o).to.deep.equal({ a: 123 }); 205 | }); 206 | it('Should throw with extra single quotes within key', function () { 207 | expect(function () { 208 | parse( "{''':123}" ); 209 | }).to.throw(Error); 210 | }); 211 | }); 212 | describe('Key values', function () { 213 | it('Decimal key value', function () { 214 | const o = parse( "{a:123}" ); 215 | //console.log( "o is", o ); 216 | expect(o).to.deep.equal({ a:123 }); 217 | }); 218 | it('ES6 template key value', function () { 219 | const o = parse( "{a:`abcdef`}" ); 220 | //console.log( "o is", o ); 221 | expect(o).to.deep.equal({ a: 'abcdef' }); 222 | }); 223 | 224 | it('Double-quoted key value', function () { 225 | const o = parse( "{a:\"abcdef\"}" ); 226 | //console.log( "o is", o ); 227 | expect(o).to.deep.equal({ a: 'abcdef' }); 228 | }); 229 | 230 | it('Single-quoted key value (with newline)', function () { 231 | const o = parse( "{a:'abc\ndef'}" ); 232 | //console.log( "o is", o ); 233 | expect(o).to.deep.equal({ a: 'abc\ndef' }); 234 | }); 235 | 236 | it('Single-quoted key value (with trailing backslash and newline)', function () { 237 | const o = parse( "{a:'abc\\\ndef'}" ); 238 | //console.log( "o is", o ); 239 | expect(o).to.deep.equal({ a: 'abcdef' }); 240 | }); 241 | 242 | it('Single-quoted key value with backslash, carriage return, and newline', function () { 243 | const o = parse( "{a:'abc\\\r\ndef'}" ); 244 | //console.log( "o is", o ); 245 | expect(o).to.deep.equal({ a: 'abcdef' }); 246 | }); 247 | it('Single-quoted key value with backslash and line separator', function () { 248 | const o = parse( "{a:'abc\\\u2028def'}" ); 249 | //console.log( "o is", o ); 250 | expect(o).to.deep.equal({ a: 'abcdef' }); 251 | }); 252 | it('Single-quoted key value with backslash and paragraph separator', function () { 253 | const o = parse( "{a:'abc\\\u2029def'}" ); 254 | //console.log( "o is", o ); 255 | expect(o).to.deep.equal({ a: 'abcdef' }); 256 | }); 257 | it('Unquoted keyword (true)', function () { 258 | const o = parse( "{ true:1 }" ); 259 | //console.log( "o is", o ); 260 | expect(o).to.deep.equal({ true: 1 }); 261 | }); 262 | it('Unquoted keyword (null)', function () { 263 | const o = parse( "{ null:1 }" ); 264 | //console.log( "o is", o ); 265 | expect(o).to.deep.equal({ null: 1 }); 266 | }); 267 | it('Handles trailing commas', function () { 268 | const o = parse(`{ 269 | abc: { 270 | 'a': 5, 271 | } 272 | }`); 273 | //console.log( "o is", o ); 274 | expect(o).to.deep.equal({ 275 | abc: { 276 | a: 5, 277 | } 278 | }); 279 | }); 280 | }); 281 | 282 | it('Handles trailing commas', function () { 283 | const o = parse(`{ 284 | abc: { 285 | 'a': 5, 286 | } 287 | }`); 288 | //console.log( "o is", o ); 289 | expect(o).to.deep.equal({ 290 | abc: { 291 | a: 5, 292 | } 293 | }); 294 | }); 295 | }); 296 | describe('Arrays', function () { 297 | it('Simple array', function () { 298 | const o = parse( "[123]" ); 299 | //console.log( "o is", o ); 300 | expect(o).to.deep.equal([123]); 301 | }); 302 | }); 303 | }); 304 | 305 | describe('Parsing with reviver', function () { 306 | it('With simple reviver', function () { 307 | const results = []; 308 | const o = parse( "{\"a\":{\"b\":{\"c\":{\"d\":123}, e:456}, f:789}, g: 987}", function (a, b) { 309 | results.push([a, b]); 310 | //console.log( a, b ); 311 | return b; 312 | } ); 313 | //console.log( "o is", JSON.stringify( o ) ); 314 | 315 | expect(o).to.deep.equal({ 316 | a: {b: {c: {d: 123}, e:456}, f:789}, g: 987 317 | }); 318 | expect(results).to.deep.equal([ 319 | ['d', 123], 320 | ['c', {d: 123}], 321 | ['e', 456], 322 | ['b', {c: {d: 123}, e:456}], 323 | ['f', 789], 324 | ['a', {b: {c: {d: 123}, e:456}, f:789}], 325 | ['g', 987], 326 | ['', {a: {b: {c: {d: 123}, e:456}, f:789}, g: 987}], 327 | ]); 328 | }); 329 | it('Reviver which deletes', function () { 330 | const results = []; 331 | // Add temporarily to prototype to check coverage of 332 | // `hasOwnProperty` filter 333 | Object.prototype.ttt = function () {}; 334 | const o = parse('{a: {b: {c: 5}, d: 8}}', function (a, b) { 335 | results.push([a, b]); 336 | if (a === 'd') { 337 | return undefined; 338 | } 339 | return b; 340 | } ); 341 | //console.log( "o is", JSON.stringify( o ) ); 342 | delete Object.prototype.ttt; 343 | 344 | expect(o).to.deep.equal({ 345 | a: {b: {c: 5}} 346 | }); 347 | 348 | expect(results).to.deep.equal([ 349 | ['c', 5], 350 | ['b', { 351 | c: 5 352 | }], 353 | ['d', 8], 354 | ['a', { 355 | b: { 356 | c: 5 357 | } 358 | }], 359 | ['', { 360 | a: { 361 | b: { 362 | c: 5 363 | } 364 | } 365 | }] 366 | ]); 367 | }); 368 | }); 369 | -------------------------------------------------------------------------------- /test/json6TestObject2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | const obj = { 5 | "name": "@std/esm", 6 | "version": "0.18.0", 7 | "description": "Enable ES modules in Node today!", 8 | "keywords": "commonjs, ecmascript, export, import, modules, node, require", 9 | "repository": "standard-things/esm", 10 | "license": "MIT", 11 | "author": "John-David Dalton ", 12 | "main": "index.js", 13 | "private": true, 14 | "@std/esm": true, 15 | "engines": { 16 | "node": ">=4" 17 | }, 18 | "scripts": { 19 | "prebuild:prod": "optional-dev-dependency", 20 | "precommit": "npm run lint", 21 | "prelint": "npm run pretest", 22 | "prepub": "npm run test:prod", 23 | "pretest": "npm run build -- --test", 24 | "pretest:prod": "npm run build:prod -- --test", 25 | "build": "node script/build.js", 26 | "build:prod": "npm run build -- --prod", 27 | "clean": "node script/clean.js", 28 | "lint": "eslint \\'**/*.{js,mjs}\\' --fix --quiet", 29 | "pub": "node script/publish.js", 30 | "test": "node script/test.js", 31 | "test:prod": "node script/test.js --prod" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.0.0-beta.34", 35 | "@babel/plugin-proposal-class-properties": "^7.0.0-beta.34", 36 | "@babel/plugin-transform-block-scoping": "^7.0.0-beta.34", 37 | "@babel/preset-env": "^7.0.0-beta.34", 38 | "@babel/register": "^7.0.0-beta.34", 39 | "acorn": "^5.2.1", 40 | "ava": "^0.24.0", 41 | "babel-eslint": "^8.0.3", 42 | "babel-loader": "^8.0.0-beta.0", 43 | "babel-plugin-transform-for-of-as-array": "^1.0.4", 44 | "download": "^6.2.5", 45 | "eslint": "^4.12.0", 46 | "eslint-plugin-import": "^2.7.0", 47 | "eslint-plugin-node": "^5.2.0", 48 | "execa": "^0.8.0", 49 | "fs-extra": "^4.0.3", 50 | "globby": "^7.1.1", 51 | "husky": "^0.14.3", 52 | "jest": "^21.2.1", 53 | "json-6": "^0.1.120", 54 | "minizlib": "^1.0.4", 55 | "mocha": "^4.0.1", 56 | "mock-stdio": "^1.0.0", 57 | "nop": "^1.0.0", 58 | "nyc": "^11.3.0", 59 | "optimize-js-plugin": "0.0.4", 60 | "optional-dev-dependency": "^2.0.1", 61 | "pify": "^3.0.0", 62 | "pm2": "^2.8.0", 63 | "semver": "^5.4.1", 64 | "trash": "^4.2.1", 65 | "typescript": "^2.6.1", 66 | "uglify-es": "^3.2.1", 67 | "uglifyjs-webpack-plugin": "^1.1.1", 68 | "webpack": "^3.10.0", 69 | "webpack-bundle-analyzer": "^2.8.3", 70 | "webpack-common-shake": "^1.5.3", 71 | "yargs": "^10.0.3" 72 | }, 73 | "optionalDevDependencies": { 74 | "node-zopfli": "^2.0.2" 75 | }, 76 | "files": [ 77 | "index.js", 78 | "esm.js.gz" 79 | ] 80 | }; 81 | 82 | describe('JSON6 test object 2', function () { 83 | it('Parses (`package.json`) object', function () { 84 | const result = JSON6.parse( JSON.stringify(obj) ); 85 | //console.log(result); 86 | expect(result).to.deep.equal(obj); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/json6TestObjectArray.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require('..'); 3 | 4 | describe('Objects and arrays', function () { 5 | it('Simple array with number', function () { 6 | const result = JSON6.parse( "[1234]" ); 7 | expect(result).to.deep.equal([1234]); 8 | }); 9 | it('Simple nested array with number', function () { 10 | const result = JSON6.parse( "[1,[2,[3,[4,5]]]]" ); 11 | expect(result).to.deep.equal([1, [2, [3, [4, 5]]]]); 12 | }); 13 | it('Array of objects', function () { 14 | const d = '[ {a: "", b: ""}, {a: "", b: ""} ]'; 15 | const result = JSON6.parse( d ); 16 | expect(result).to.deep.equal([ {a: "", b: ""}, {a: "", b: ""} ]); 17 | }); 18 | it('Array with various types', function () { 19 | const d = '[true, false, -NaN, NaN, -Infinity, Infinity, null, undefined]'; 20 | const result = JSON6.parse( d ); 21 | expect(result).to.deep.equal([ 22 | true, false, -NaN, NaN, -Infinity, Infinity, null, undefined 23 | ]); 24 | }); 25 | it('Sparse array', function () { 26 | const d = '[, ,]'; 27 | const result = JSON6.parse( d ); 28 | expect(result).to.deep.equal([ 29 | undefined, undefined 30 | ]); 31 | }); 32 | it('Object with various types', function () { 33 | const d = '{a: true, b: false, c: -NaN, d: NaN, e: -Infinity, f: Infinity, g: undefined, h: null}'; 34 | const result = JSON6.parse( d ); 35 | expect(result).to.deep.equal({ 36 | a: true, b: false, c: -NaN, d: NaN, e: -Infinity, f: Infinity, g: undefined, h: null 37 | }); 38 | }); 39 | it('Array with empty object', function () { 40 | const d = '[{}]'; 41 | const result = JSON6.parse( d ); 42 | expect(result).to.deep.equal([ 43 | {} 44 | ]); 45 | }); 46 | it('Array of objects and array', function () { 47 | const d = '[ {a: "", b: ""}, [1,2], {a: "", b: ""} ]'; 48 | const result = JSON6.parse( d ); 49 | expect(result).to.deep.equal([ {a: "", b: ""}, [1, 2], {a: "", b: ""} ]); 50 | }); 51 | it('Object with child objects and arrays', function () { 52 | const d = '{ a:{a: "", b: ""}, b:[{d:"",e:""},{f:"",g:""}], c:{a: "", b: ""} }'; 53 | const result = JSON6.parse( d ); 54 | expect(result).to.deep.equal({ 55 | a: {a: "", b: ""}, b: [{d:"",e:""}, {f:"",g:""}], c: {a: "", b: ""} 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/json6_internals_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | // this is more about coverage than failure/success. 5 | // internally there can be multiple outstanding buffer segments to process, this just makes sure we can do that. 6 | // Failure is any thrown error. 7 | 8 | describe('JSON6.streaming.internal', function () { 9 | it('requeues input buffers for pending inputs', function () { 10 | const results = []; 11 | const parser = JSON6.begin(); 12 | let parseResult; 13 | 14 | parseResult = parser._write( "1 2 3 " ); 15 | results.push( parseResult ); 16 | results.push( parser.value() ); 17 | 18 | parseResult = parser._write( "1 2 3 4 5 " ); 19 | results.push( parseResult ); 20 | results.push( parser.value() ); 21 | 22 | parseResult = parser._write( "1 2 3 " ); 23 | parseResult = parser._write(); 24 | results.push( parseResult ); 25 | results.push( parser.value() ); 26 | 27 | while( (parseResult = parser._write()) ) { 28 | //console.log( "Leftover Data:", parser.value() ); 29 | } 30 | 31 | // and now, there will be a 'saved' which push() can pull from. 32 | parseResult = parser._write( "1 " ); 33 | //console.log( "Leftover Data:", parser.value() ); 34 | 35 | 36 | expect(results.join(",")).to.equal("2,1,2,2,1,3"); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/numberTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON = require( '..' ); 3 | 4 | describe('Numbers', function () { 5 | it('Decimal', function () { 6 | const n = .123; 7 | //console.log( "typeof( n ) =", typeof n, n ); 8 | 9 | const result = JSON.parse( '.123' ); 10 | //console.log( "typeof( result ) =", typeof result, result ); 11 | 12 | expect(result).to.equal(n); 13 | }); 14 | it('Positive decimal', function () { 15 | const n = +.123; 16 | //console.log( "typeof( n ) =", typeof n, n ); 17 | 18 | const result = JSON.parse( '+.123' ); 19 | //console.log( "typeof( result ) =", typeof result, result ); 20 | 21 | expect(result).to.equal(n); 22 | }); 23 | 24 | it('Negative decimal', function () { 25 | const n = -.123; 26 | //console.log( "typeof( n ) =", typeof n, n ); 27 | 28 | const result = JSON.parse( '-.123' ); 29 | //console.log( "typeof( result ) =", typeof result, result ); 30 | 31 | expect(result).to.equal(n); 32 | }); 33 | 34 | it('Decimal with scientific notation', function () { 35 | const n = .123e3; 36 | //console.log( "typeof( n ) =", typeof n, n ); 37 | 38 | const result = JSON.parse( '.123e3' ); 39 | //console.log( "typeof( result ) =", typeof result, result ); 40 | 41 | expect(result).to.equal(n); 42 | }); 43 | 44 | it('Decimal ending prematurely (throws)', function () { 45 | expect(function () { 46 | JSON.parse( '14g' ); 47 | }).to.throw(Error); 48 | }); 49 | 50 | it('Decimal with bad scientific notation (throws)', function () { 51 | expect(function () { 52 | JSON.parse( '1ee' ); 53 | }).to.throw(Error); 54 | }); 55 | 56 | it('Decimal with positive scientific notation', function () { 57 | const n = .123e+3; 58 | //console.log( "typeof( n ) =", typeof n, n ); 59 | 60 | const result = JSON.parse( '.123e+3' ); 61 | //console.log( "typeof( result ) =", typeof result, result ); 62 | 63 | expect(result).to.equal(n); 64 | }); 65 | 66 | it('Decimal with negative scientific notation', function () { 67 | const n = .123e-3; 68 | //console.log( "typeof( n ) =", typeof n, n ); 69 | 70 | const result = JSON.parse( '.123e-3' ); 71 | //console.log( "typeof( result ) =", typeof result, result ); 72 | 73 | expect(result).to.equal(n); 74 | }); 75 | 76 | it('Hexadecimal', function () { 77 | const n = 0x123; 78 | //console.log( "typeof( n ) =", typeof n, n ); 79 | 80 | const result = JSON.parse( '0x123' ); 81 | //console.log( "typeof( result ) =", typeof result, result ); 82 | 83 | expect(result).to.equal(n); 84 | }); 85 | 86 | it('Hexadecimal 2', function () { 87 | const n = 0x1af; 88 | //console.log( "typeof( n ) =", typeof n, n ); 89 | 90 | const result = JSON.parse( '0x1af' ); 91 | //console.log( "typeof( result ) =", typeof result, result ); 92 | 93 | expect(result).to.equal(n); 94 | }); 95 | 96 | function failSuccess( string ) { 97 | it('Fails with "' + string + '"', function () { 98 | expect(function () { 99 | JSON.parse( string ); 100 | }).to.throw(Error); 101 | }); 102 | } 103 | 104 | failSuccess( ".123-45" ); 105 | failSuccess( ".123e2-45" ); 106 | failSuccess( ".123e--45" ); 107 | failSuccess( ".123e+-45" ); 108 | failSuccess( ".123e3-45" ); 109 | failSuccess( ".05x23" ); 110 | failSuccess( "0xx23" ); 111 | failSuccess( "0x23.45" ); 112 | }); 113 | -------------------------------------------------------------------------------- /test/stream.json6: -------------------------------------------------------------------------------- 1 | 123 2 | 456 3 | 789 4 | 1234 5 | ['a','b','c'] 6 | `This 7 | is 8 | a 9 | test` 10 | { a: { b : { c : { d : 123 }, e: [154,452] }, f : 942 }, g: 'Final' } 11 | -------------------------------------------------------------------------------- /test/streamTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( '..' ); 3 | const fs = require( 'fs' ); 4 | const path = require( 'path' ); 5 | 6 | const buf = fs.readFileSync( path.join(__dirname, 'stream.json6') ); 7 | const msg = buf.toString( 'utf8' ); 8 | 9 | describe('Streaming', function () { 10 | it('Streams various objects', function () { 11 | const results = []; 12 | const parser = JSON6.begin(function (val) { 13 | //console.log( "Got Object:", val ); 14 | results.push(val); 15 | }); 16 | 17 | for( 18 | let result = parser.write( msg ); 19 | result > 0; 20 | parser.write() 21 | ); 22 | 23 | expect(results).to.deep.equal([ 24 | 123, 25 | 456, 26 | 789, 27 | 1234, 28 | ['a','b','c'], 29 | 'This\nis\na\ntest', 30 | { a: { b : { c : { d : 123 }, e: [154,452] }, f : 942 }, g: 'Final' } 31 | ]); 32 | }); 33 | it('Converts non-string to string and attempts to process', function () { 34 | const results = []; 35 | const parser = JSON6.begin(function (val) { 36 | //console.log( "Got Object:", val ); 37 | results.push(val); 38 | }); 39 | 40 | expect(function () { 41 | parser.write({}); 42 | }).to.throw(Error, /fault parsing 'o' unexpected/); 43 | }); 44 | it('handles incomplete string key in chunks', function () { 45 | const results = []; 46 | const parser = JSON6.begin(function (val) { 47 | //console.log( "Got Object:", val ); 48 | results.push(val); 49 | }); 50 | 51 | for( 52 | let result = parser.write( '{"' ); 53 | result > 0; 54 | parser.write() 55 | ); 56 | parser.write( 'a' ); 57 | parser.write( '"' ); 58 | 59 | expect(results).to.deep.equal([]); 60 | }); 61 | it('Supports reviver', function () { 62 | const results = []; 63 | const parser = JSON6.begin(function (/*val*/) { 64 | //console.log( "Got Object:", val ); 65 | }, function (a, b) { 66 | results.push([a, b]); 67 | if (a === 'd') { 68 | return undefined; 69 | } 70 | return b; 71 | } ); 72 | 73 | // Add temporarily to prototype to check coverage of 74 | // `hasOwnProperty` filter 75 | Object.prototype.ttt = function () {}; 76 | parser.write('{a: {b: {c: 5}, d: 8}}'); 77 | delete Object.prototype.ttt; 78 | 79 | expect(results).to.deep.equal([ 80 | ['c', 5], 81 | ['b', { 82 | c: 5 83 | }], 84 | ['d', 8], 85 | ['a', { 86 | b: { 87 | c: 5 88 | } 89 | }], 90 | ['', { 91 | a: { 92 | b: { 93 | c: 5 94 | } 95 | } 96 | }] 97 | ]); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/stringEscapes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( '..' ); 3 | 4 | describe('String escapes', function () { 5 | describe('Byte order mark', function () { 6 | it('Does not parses byte order mark', function () { 7 | const result = JSON6.parse( '"abc"\uFEFF' ); 8 | expect(result).to.equal('abc'); 9 | }); 10 | }); 11 | describe('Octal escapes', function () { 12 | it('Does not parses string octal escape', function () { 13 | const result = JSON6.parse( '"\\056"' ); 14 | expect(result).to.equal('\0'+'56'); 15 | }); 16 | it('Does not parse string octal escape followed by character', function () { 17 | const result = JSON6.parse( '"\\01A"' ); 18 | expect(result).to.equal('\0' + '1A'); 19 | }); 20 | }); 21 | describe('Unicode escape', function () { 22 | it('Throws with bad Unicode escape', function () { 23 | expect(function () { 24 | JSON6.parse( '"\\u00G"' ); 25 | }).to.throw(Error, /escaped character, parsing hex/); 26 | }); 27 | }); 28 | describe('Unicode wide escapes', function () { 29 | it('Parses Unicode wide escape (lower-case)', function () { 30 | const result = JSON6.parse( '"\\u{002e}"' ); 31 | expect(result).to.equal('.'); 32 | }); 33 | it('Parses Unicode wide escape (upper-case)', function () { 34 | const result = JSON6.parse( '"\\u{002E}"' ); 35 | expect(result).to.equal('.'); 36 | }); 37 | it('Throws with bad Unicode wide escape (upper-case)', function () { 38 | expect(function () { 39 | JSON6.parse( '"\\u{00G}"' ); 40 | }).to.throw(Error, /escaped character, parsing hex/); 41 | }); 42 | 43 | it('Throws with incomplete Unicode wide escape (upper-case)', function () { 44 | expect(function () { 45 | JSON6.parse( '"\\u{00F"' ); 46 | }).to.throw(Error, /Incomplete long unicode sequence/); 47 | }); 48 | }); 49 | describe('String hex escapes', function () { 50 | it('Parses string hex', function () { 51 | const result = JSON6.parse( '"\\x2e"' ); 52 | expect(result).to.equal('.'); 53 | }); 54 | it('Throws with bad hex escape', function () { 55 | expect(function () { 56 | JSON6.parse( '"\\x0G"' ); 57 | }).to.throw(Error, /escaped character, parsing hex/); 58 | }); 59 | }); 60 | describe('Single escapes', function () { 61 | it('\\b', function () { 62 | const result = JSON6.parse( '"\\b"' ); 63 | expect(result).to.equal('\b'); 64 | }); 65 | it('\\f', function () { 66 | const result = JSON6.parse( '"\\f"' ); 67 | expect(result).to.equal('\f'); 68 | }); 69 | it('\\v', function () { 70 | const result = JSON6.parse( '"\\v"' ); 71 | expect(result).to.equal('\v'); 72 | }); 73 | it('Should throw with string closing without successor to backslash', function () { 74 | expect(function () { 75 | JSON6.parse( '"\\"' ); 76 | }).to.throw(Error); 77 | }); 78 | 79 | it('should consume carriage return escape at end of string', function () { 80 | const o = JSON6.parse( '"\\\r"' ); 81 | expect(o).to.equal(''); 82 | }); 83 | 84 | it('should recover character after carriage return escape at end of string', function () { 85 | const o = JSON6.parse( '"\\\rA"' ); 86 | expect(o).to.equal('A'); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/test-single-json6.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | describe('Single JSON6', function () { 5 | it('Single JSON6', function () { 6 | const obj = JSON6.parse( "{ asdf : 1234 } " ); 7 | expect(obj).to.deep.equal({ 8 | asdf: 1234 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/testIncompleteStringEscapes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JSON6 = require( ".." ); 4 | 5 | const parse = JSON6.parse; 6 | 7 | describe('Incomplete String Escape tests', function () { 8 | it('Incomplete string escapes', function () { 9 | expect(function () { 10 | parse( "'\\x1'" ); 11 | }).to.throw(Error); 12 | 13 | expect(function () { 14 | parse( "'\\u31'" ); 15 | }).to.throw(Error); 16 | 17 | expect(function () { 18 | parse( "'\\u{0'" ); 19 | }).to.throw(Error); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/testJsonDecode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const JSON6 = require( ".." ); 3 | 4 | describe('JSON decoding', function () { 5 | it('Unicode escapes', function () { 6 | const result = JSON6.parse( '"\\u004D\\u004e\\u004F\\u0050"' ); 7 | //console.log( "MNOP=", result ); 8 | expect(result).to.equal('MNOP'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/testjson6.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // var JSON6 = require( "./json6.js" ); 3 | const JSON6 = require( '..' ); 4 | 5 | describe('JSON streaming', function () { 6 | it('Parses multiple and split strings', function () { 7 | let lastval; 8 | let skip_out = true; 9 | const results = []; 10 | const parser = JSON6.begin(function (val) { 11 | lastval = val; 12 | if( !skip_out ) { 13 | results.push(val); 14 | } 15 | }); 16 | 17 | const complexSplit = [ 18 | "db", 19 | { 20 | "_": { 21 | "#": "db", 22 | ">": { 23 | "j6bjv": 1502678337047 24 | } 25 | }, 26 | "j6bjr5rg": { 27 | "#": "j6bjzqK" 28 | } 29 | } 30 | ]; 31 | const complexSplitString = JSON.stringify(complexSplit); 32 | const testOut = complexSplitString; 33 | 34 | for( let n = 1; n < complexSplitString.length; n++ ) { 35 | const a = complexSplitString.substr( 0, n ); 36 | const b = complexSplitString.substr( n ); 37 | // console.log( "parse:\n", JSON.stringify( a ), "\n", JSON.stringify(b)); 38 | if( !a || !b ) continue; 39 | parser.write( a ); 40 | parser.write( b ); 41 | if( JSON.stringify( lastval ) != testOut ) { 42 | expect( 43 | false, 44 | "FAILED REASSEMBLY AT " + n + 45 | '\n got:\n' + JSON.stringify( lastval ) + 46 | '\n Original:\n' + testOut 47 | ).to.be.true; 48 | } 49 | // console.log( "Tested:", JSON.stringify(a), JSON.stringify(b)); 50 | } 51 | 52 | skip_out = false; 53 | 54 | parser.write( '[]' ); 55 | parser.write( '[,]' ); 56 | parser.write( '[,,]' ); 57 | 58 | const obj = [ 59 | "db", 60 | { 61 | "_": { 62 | "#": "db", 63 | ">": { 64 | "j6bjv": 1502678337047 65 | } 66 | }, 67 | "j6bjr5rg": { 68 | "#": "j6bjzqK" 69 | } 70 | } 71 | ]; 72 | const str = JSON.stringify(obj); 73 | const pos = str.indexOf('5rg'); 74 | 75 | 76 | parser.write(str.slice(0, pos)); 77 | parser.write(str.slice(pos)); 78 | 79 | 80 | parser.write( "123" ); 81 | 82 | parser.write( "[\n null,\n null\n]" ); 83 | 84 | parser.write( '"Hello ' ); // a broken simple value string, results as 'Hello World!' 85 | parser.write( 'World!"' ); 86 | parser.write( '{ first: 1,' ); // a broken structure 87 | parser.write( ' second : 2 }' ); 88 | parser.write( '[1234,12'); // a broken array across a value 89 | parser.write( '34,1234]'); 90 | parser.write( '1234 456 789 123 523'); // multiple single simple values that are numbers 91 | parser.write( '{a:1} {b:2} {c:3}'); // multiple objects 92 | 93 | parser.write( '1234' ); // this won't return immediately, there might be more numeric data. 94 | parser.write( '' ); // flush any pending numbers; if an object or array or string was split, throws an error; missing close. 95 | 96 | parser.write( '1234' ); 97 | parser.write( '5678 ' ); // at this point, the space will flush the number value '12345678' 98 | expect(results).to.deep.equal([ 99 | [], 100 | /* eslint-disable no-sparse-arrays */ 101 | [,], 102 | [,,], 103 | /* eslint-enable no-sparse-arrays */ 104 | obj, 105 | 123, 106 | [null, null], 107 | 'Hello World!', 108 | { first: 1, second : 2 }, 109 | [1234, 1234, 1234], 110 | 1234, 456, 789, 123, 523, 111 | {a: 1}, 112 | {b: 2}, 113 | {c: 3}, 114 | 1234, 115 | 12345678 116 | ]); 117 | }); 118 | }); 119 | --------------------------------------------------------------------------------