├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.txt ├── Makefile ├── README.md ├── build ├── circular-json.amd.js ├── circular-json.js ├── circular-json.max.amd.js ├── circular-json.max.js └── circular-json.node.js ├── coverage ├── coverage.json ├── lcov-report │ ├── base.css │ ├── build │ │ ├── circular-json.node.js.html │ │ └── index.html │ ├── index.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ └── sorter.js └── lcov.info ├── index.html ├── package.json ├── src └── circular-json.js ├── template ├── amd.after ├── amd.before ├── copyright ├── license.after ├── license.before ├── md.after ├── md.before ├── node.after ├── node.before ├── var.after └── var.before └── test ├── benchmark.js └── circular-json.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # not working due missing www. 5 | open_collective: # hyperHTML 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: https://www.patreon.com/webreflection 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | src/* 3 | test/* 4 | template/* 5 | node_modules/* 6 | build/*.amd.js 7 | .gitignore 8 | .travis.yml 9 | index.html 10 | Makefile 11 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | - 6 5 | - 8 6 | - stable 7 | git: 8 | depth: 1 9 | branches: 10 | only: 11 | - master 12 | after_success: 13 | - "npm run coveralls" 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013-2017 by Andrea Giammarchi - @WebReflection 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build var node amd size hint clean test web preview pages dependencies 2 | 3 | # repository name 4 | REPO = circular-json 5 | 6 | # make var files 7 | VAR = src/$(REPO).js 8 | 9 | # make node files 10 | NODE = $(VAR) 11 | 12 | # make amd files 13 | AMD = $(VAR) 14 | 15 | 16 | # README constant 17 | 18 | 19 | # default build task 20 | build: 21 | make var 22 | make node 23 | make amd 24 | make test 25 | # make hint 26 | make size 27 | 28 | # build generic version 29 | var: 30 | mkdir -p build 31 | cat template/var.before $(VAR) template/var.after >build/no-copy.$(REPO).max.js 32 | node node_modules/uglify-js/bin/uglifyjs --verbose build/no-copy.$(REPO).max.js >build/no-copy.$(REPO).js 33 | cat template/license.before LICENSE.txt template/license.after build/no-copy.$(REPO).max.js >build/$(REPO).max.js 34 | cat template/copyright build/no-copy.$(REPO).js >build/$(REPO).js 35 | rm build/no-copy.$(REPO).max.js 36 | rm build/no-copy.$(REPO).js 37 | 38 | # build node.js version 39 | node: 40 | mkdir -p build 41 | cat template/license.before LICENSE.txt template/license.after template/node.before $(NODE) template/node.after >build/$(REPO).node.js 42 | 43 | # build AMD version 44 | amd: 45 | mkdir -p build 46 | cat template/amd.before $(AMD) template/amd.after >build/no-copy.$(REPO).max.amd.js 47 | node node_modules/uglify-js/bin/uglifyjs --verbose build/no-copy.$(REPO).max.amd.js >build/no-copy.$(REPO).amd.js 48 | cat template/license.before LICENSE.txt template/license.after build/no-copy.$(REPO).max.amd.js >build/$(REPO).max.amd.js 49 | cat template/copyright build/no-copy.$(REPO).amd.js >build/$(REPO).amd.js 50 | rm build/no-copy.$(REPO).max.amd.js 51 | rm build/no-copy.$(REPO).amd.js 52 | 53 | size: 54 | wc -c build/$(REPO).max.js 55 | gzip -c build/$(REPO).js | wc -c 56 | 57 | # hint built file 58 | hint: 59 | node node_modules/jshint/bin/jshint build/$(REPO).max.js 60 | 61 | # clean/remove build folder 62 | clean: 63 | rm -rf build 64 | 65 | # tests, as usual and of course 66 | test: 67 | npm test 68 | 69 | # launch tiny-cdn (ctrl+click to open the page) 70 | web: 71 | node node_modules/.bin/tiny-cdn run ./ 72 | 73 | # markdown the readme and view it 74 | preview: 75 | node_modules/markdown/bin/md2html.js README.md >README.md.htm 76 | cat template/md.before README.md.htm template/md.after >README.md.html 77 | open README.md.html 78 | sleep 3 79 | rm README.md.htm README.md.html 80 | 81 | pages: 82 | git pull --rebase 83 | make var 84 | mkdir -p ~/tmp 85 | mkdir -p ~/tmp/$(REPO) 86 | cp -rf src ~/tmp/$(REPO) 87 | cp -rf build ~/tmp/$(REPO) 88 | cp -rf test ~/tmp/$(REPO) 89 | cp index.html ~/tmp/$(REPO) 90 | git checkout gh-pages 91 | mkdir -p test 92 | rm -rf test 93 | cp -rf ~/tmp/$(REPO) test 94 | git add test 95 | git add test/. 96 | git commit -m 'automatic test generator' 97 | git push 98 | git checkout master 99 | rm -r ~/tmp/$(REPO) 100 | 101 | # modules used in this repo 102 | dependencies: 103 | rm -rf node_modules 104 | mkdir node_modules 105 | npm install wru 106 | npm install tiny-cdn 107 | npm install markdown 108 | npm install uglify-js@1 109 | npm install jshint 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CircularJSON 2 | ============ 3 | 4 | [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/WebReflection/donate) ![Downloads](https://img.shields.io/npm/dm/circular-json.svg) [![Build Status](https://travis-ci.org/WebReflection/circular-json.svg?branch=master)](https://travis-ci.org/WebReflection/circular-json) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/circular-json/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/circular-json?branch=master) 5 | 6 | Serializes and deserializes otherwise valid JSON objects containing circular references into and from a specialized JSON format. 7 | 8 | - - - 9 | 10 | ## The future of this module is called [flatted](https://github.com/WebReflection/flatted#flatted) 11 | 12 | Smaller, faster, and able to produce on average a reduced output too, [flatted](https://github.com/WebReflection/flatted#flatted) is the new, bloatless, ESM and CJS compatible, circular JSON parser. 13 | 14 | It has now reached V1 and it implements the exact same JSON API. 15 | 16 | Please note **CircularJSON is in maintenance only** and **[flatted](https://github.com/WebReflection/flatted#flatted) is its successor**. 17 | 18 | - - - 19 | 20 | ### A Working Solution To A Common Problem 21 | A usage example: 22 | 23 | ```JavaScript 24 | var object = {}; 25 | object.arr = [ 26 | object, object 27 | ]; 28 | object.arr.push(object.arr); 29 | object.obj = object; 30 | 31 | var serialized = CircularJSON.stringify(object); 32 | // '{"arr":["~","~","~arr"],"obj":"~"}' 33 | // NOTE: CircularJSON DOES NOT parse JS 34 | // it handles receiver and reviver callbacks 35 | 36 | var unserialized = CircularJSON.parse(serialized); 37 | // { arr: [ [Circular], [Circular] ], 38 | // obj: [Circular] } 39 | 40 | unserialized.obj === unserialized; 41 | unserialized.arr[0] === unserialized; 42 | unserialized.arr.pop() === unserialized.arr; 43 | ``` 44 | 45 | A quick summary: 46 | 47 | * **new** in version `0.5`, you can specify a JSON parser different from JSON itself. `CircularJSON.parser = ABetterJSON;` is all you need. 48 | * uses `~` as a special prefix symbol to denote which parent the reference belongs to (i.e. `~root~child1~child2`) 49 | * reasonably fast in both serialization and deserialization 50 | * compact serialization for easier and slimmer transportation across environments 51 | * [tested and covered](test/circular-json.js) over nasty structures too 52 | * compatible with all JavaScript engines 53 | 54 | Node Installation & Usage 55 | ============ 56 | 57 | ```bash 58 | npm install --save circular-json 59 | ``` 60 | 61 | ```javascript 62 | 'use strict'; 63 | 64 | var 65 | CircularJSON = require('circular-json'), 66 | obj = { foo: 'bar' }, 67 | str 68 | ; 69 | 70 | obj.self = obj; 71 | str = CircularJSON.stringify(obj); 72 | ``` 73 | 74 | There are no dependencies. 75 | 76 | Browser Installation & Usage 77 | ================ 78 | 79 | * Global: 80 | * AMD: 81 | * CommonJS: 82 | 83 | (generated via [gitstrap](https://github.com/WebReflection/gitstrap)) 84 | 85 | ```html 86 | 87 | ``` 88 | 89 | ```javascript 90 | 'use strict'; 91 | 92 | var CircularJSON = window.CircularJSON 93 | , obj = { foo: 'bar' } 94 | , str 95 | ; 96 | 97 | obj.self = obj; 98 | str = CircularJSON.stringify(obj); 99 | ``` 100 | 101 | NOTE: Platforms without native JSON (i.e. MSIE <= 8) requires `json3.js` or similar. 102 | 103 | It is also *a bad idea* to `CircularJSON.parse(JSON.stringify(object))` because of those manipulation used in `CircularJSON.stringify()` able to make parsing safe and secure. 104 | 105 | As summary: `CircularJSON.parse(CircularJSON.stringify(object))` is the way to go, same is for `JSON.parse(JSON.stringify(object))`. 106 | 107 | API 108 | === 109 | 110 | It's the same as native JSON, except the fourth parameter `placeholder`, which circular references to be replaced with `"[Circular]"` (i.e. for logging). 111 | 112 | * CircularJSON.stringify(object, replacer, spacer, placeholder) 113 | * CircularJSON.parse(string, reviver) 114 | 115 | Bear in mind `JSON.parse(CircularJSON.stringify(object))` will work but not produce the expected output. 116 | 117 | Similar Libraries 118 | ======= 119 | 120 | ### Why Not the [@izs](https://twitter.com/izs) One 121 | The module [json-stringify-safe](https://github.com/isaacs/json-stringify-safe) seems to be for `console.log()` but it's completely pointless for `JSON.parse()`, being latter one unable to retrieve back the initial structure. Here an example: 122 | 123 | ```JavaScript 124 | // a logged object with circular references 125 | { 126 | "circularRef": "[Circular]", 127 | "list": [ 128 | "[Circular]", 129 | "[Circular]" 130 | ] 131 | } 132 | // what do we do with above output ? 133 | ``` 134 | 135 | Just type this in your `node` console: `var o = {}; o.a = o; console.log(o);`. The output will be `{ a: [Circular] }` ... good, but that ain't really solving the problem. 136 | 137 | However, if that's all you need, the function used to create that kind of output is probably faster than `CircularJSON` and surely fits in less lines of code. 138 | 139 | 140 | ### Why Not {{put random name}} Solution 141 | So here the thing: circular references can be wrong but, if there is a need for them, any attempt to ignore them or remove them can be considered just a failure. 142 | 143 | Not because the method is bad or it's not working, simply because the circular info, the one we needed and used in the first place, is lost! 144 | 145 | In this case, `CircularJSON` does even more than just solve circular and recursions: it maps all same objects so that less memory is used as well on deserialization as less bandwidth too! 146 | It's able to redefine those references back later on so the way we store is the way we retrieve and in a reasonably performant way, also trusting the snappy and native `JSON` methods to iterate. 147 | -------------------------------------------------------------------------------- /build/circular-json.amd.js: -------------------------------------------------------------------------------- 1 | /*! (C) WebReflection Mit Style License */ 2 | define(function(JSON,RegExp){var specialChar="~",safeSpecialChar="\\x"+("0"+specialChar.charCodeAt(0).toString(16)).slice(-2),escapedSafeSpecialChar="\\"+safeSpecialChar,specialCharRG=new RegExp(safeSpecialChar,"g"),safeSpecialCharRG=new RegExp(escapedSafeSpecialChar,"g"),safeStartWithSpecialCharRG=new RegExp("(?:^|([^\\\\]))"+escapedSafeSpecialChar),indexOf=[].indexOf||function(v){for(var i=this.length;i--&&this[i]!==v;);return i},$String=String;function generateReplacer(value,replacer,resolve){var doNotIgnore=false,inspect=!!replacer,path=[],all=[value],seen=[value],mapp=[resolve?specialChar:"[Circular]"],last=value,lvl=1,i,fn;if(inspect){fn=typeof replacer==="object"?function(key,value){return key!==""&&replacer.indexOf(key)<0?void 0:value}:replacer}return function(key,value){if(inspect)value=fn.call(this,key,value);if(doNotIgnore){if(last!==this){i=lvl-indexOf.call(all,this)-1;lvl-=i;all.splice(lvl,all.length);path.splice(lvl-1,path.length);last=this}if(typeof value==="object"&&value){if(indexOf.call(all,value)<0){all.push(last=value)}lvl=all.length;i=indexOf.call(seen,value);if(i<0){i=seen.push(value)-1;if(resolve){path.push((""+key).replace(specialCharRG,safeSpecialChar));mapp[i]=specialChar+path.join(specialChar)}else{mapp[i]=mapp[0]}}else{value=mapp[i]}}else{if(typeof value==="string"&&resolve){value=value.replace(safeSpecialChar,escapedSafeSpecialChar).replace(specialChar,safeSpecialChar)}}}else{doNotIgnore=true}return value}}function retrieveFromPath(current,keys){for(var i=0,length=keys.length;i 2 | 3 | 4 | Code coverage report for build/circular-json.node.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / build/ circular-json.node.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 62/62 26 |
27 |
28 | 100% 29 | Branches 30 | 56/56 31 |
32 |
33 | 100% 34 | Functions 35 | 12/12 36 |
37 |
38 | 100% 39 | Lines 40 | 59/59 41 |
42 |
43 |
44 |
45 |

 46 | 
668 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40 86 | 41 87 | 42 88 | 43 89 | 44 90 | 45 91 | 46 92 | 47 93 | 48 94 | 49 95 | 50 96 | 51 97 | 52 98 | 53 99 | 54 100 | 55 101 | 56 102 | 57 103 | 58 104 | 59 105 | 60 106 | 61 107 | 62 108 | 63 109 | 64 110 | 65 111 | 66 112 | 67 113 | 68 114 | 69 115 | 70 116 | 71 117 | 72 118 | 73 119 | 74 120 | 75 121 | 76 122 | 77 123 | 78 124 | 79 125 | 80 126 | 81 127 | 82 128 | 83 129 | 84 130 | 85 131 | 86 132 | 87 133 | 88 134 | 89 135 | 90 136 | 91 137 | 92 138 | 93 139 | 94 140 | 95 141 | 96 142 | 97 143 | 98 144 | 99 145 | 100 146 | 101 147 | 102 148 | 103 149 | 104 150 | 105 151 | 106 152 | 107 153 | 108 154 | 109 155 | 110 156 | 111 157 | 112 158 | 113 159 | 114 160 | 115 161 | 116 162 | 117 163 | 118 164 | 119 165 | 120 166 | 121 167 | 122 168 | 123 169 | 124 170 | 125 171 | 126 172 | 127 173 | 128 174 | 129 175 | 130 176 | 131 177 | 132 178 | 133 179 | 134 180 | 135 181 | 136 182 | 137 183 | 138 184 | 139 185 | 140 186 | 141 187 | 142 188 | 143 189 | 144 190 | 145 191 | 146 192 | 147 193 | 148 194 | 149 195 | 150 196 | 151 197 | 152 198 | 153 199 | 154 200 | 155 201 | 156 202 | 157 203 | 158 204 | 159 205 | 160 206 | 161 207 | 162 208 | 163 209 | 164 210 | 165 211 | 166 212 | 167 213 | 168 214 | 169 215 | 170 216 | 171 217 | 172 218 | 173 219 | 174 220 | 175 221 | 176 222 | 177 223 | 178 224 | 179 225 | 180 226 | 181 227 | 182 228 | 183 229 | 184 230 | 185 231 | 186 232 | 187 233 | 188 234 | 189 235 | 190 236 | 191 237 | 192 238 | 193 239 | 194 240 | 195 241 | 196 242 | 197 243 | 198 244 | 199 245 | 200 246 | 201 247 | 202 248 | 203 249 | 204 250 | 205 251 | 206 252 | 207 253 | 208  254 |   255 |   256 |   257 |   258 |   259 |   260 |   261 |   262 |   263 |   264 |   265 |   266 |   267 |   268 |   269 |   270 |   271 |   272 |   273 |   274 |   275 | 276 |   277 |   278 |   279 |   280 |   281 |   282 |   283 |   284 |   285 |   286 |   287 |   288 |   289 |   290 |   291 | 153× 292 | 153× 293 |   294 |   295 |   296 |   297 |   298 |   299 | 300 | 20× 301 |   302 |   303 |   304 |   305 |   306 |   307 |   308 |   309 |   310 |   311 | 20× 312 | 313 |   314 | 315 |   316 |   317 |   318 | 20× 319 |   320 |   321 |   322 |   323 | 133× 324 |   325 |   326 | 133× 327 | 113× 328 | 25× 329 | 25× 330 | 25× 331 | 25× 332 | 25× 333 |   334 |   335 | 113× 336 |   337 |   338 | 64× 339 | 48× 340 |   341 | 64× 342 | 64× 343 | 64× 344 | 36× 345 | 36× 346 |   347 | 35× 348 | 35× 349 |   350 | 351 |   352 |   353 | 28× 354 |   355 |   356 | 49× 357 |   358 |   359 |   360 | 21× 361 |   362 |   363 |   364 |   365 | 20× 366 |   367 | 133× 368 |   369 |   370 |   371 | 372 | 11× 373 |   374 |   375 |   376 | 11× 377 |   378 |   379 | 380 | 15× 381 | 112× 382 | 112× 383 | 25× 384 |   385 | 87× 386 |   387 |   388 | 87× 389 |   390 | 87× 391 |   392 |   393 |   394 | 395 | 396 | 16× 397 |   398 | 399 |   400 |   401 | 402 | 42× 403 | 86× 404 | 83× 405 |   406 |   407 | 42× 408 |   409 |   410 | 411 | 115× 412 |   413 |   414 |   415 |   416 |   417 |   418 |   419 |   420 |   421 |   422 |   423 |   424 |   425 |   426 |   427 |   428 |   429 |   430 |   431 |   432 |   433 |   434 |   435 |   436 |   437 |   438 |   439 | 440 |   441 | 20× 442 |   443 |   444 |   445 |   446 |   447 |   448 | 15× 449 |   450 |   451 |   452 |   453 |   454 |   455 |   456 |   457 |   458 |   459 | 460 |  
/*!
461 | Copyright (C) 2013-2017 by Andrea Giammarchi - @WebReflection
462 |  
463 | Permission is hereby granted, free of charge, to any person obtaining a copy
464 | of this software and associated documentation files (the "Software"), to deal
465 | in the Software without restriction, including without limitation the rights
466 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
467 | copies of the Software, and to permit persons to whom the Software is
468 | furnished to do so, subject to the following conditions:
469 |  
470 | The above copyright notice and this permission notice shall be included in
471 | all copies or substantial portions of the Software.
472 |  
473 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
474 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
475 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
476 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
477 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
478 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
479 | THE SOFTWARE.
480 |  
481 | */
482 | var
483 |   // should be a not so common char
484 |   // possibly one JSON does not encode
485 |   // possibly one encodeURIComponent does not encode
486 |   // right now this char is '~' but this might change in the future
487 |   specialChar = '~',
488 |   safeSpecialChar = '\\x' + (
489 |     '0' + specialChar.charCodeAt(0).toString(16)
490 |   ).slice(-2),
491 |   escapedSafeSpecialChar = '\\' + safeSpecialChar,
492 |   specialCharRG = new RegExp(safeSpecialChar, 'g'),
493 |   safeSpecialCharRG = new RegExp(escapedSafeSpecialChar, 'g'),
494 |  
495 |   safeStartWithSpecialCharRG = new RegExp('(?:^|([^\\\\]))' + escapedSafeSpecialChar),
496 |  
497 |   indexOf = [].indexOf || function(v){
498 |     for(var i=this.length;i--&&this[i]!==v;);
499 |     return i;
500 |   },
501 |   $String = String  // there's no way to drop warnings in JSHint
502 |                     // about new String ... well, I need that here!
503 |                     // faked, and happy linter!
504 | ;
505 |  
506 | function generateReplacer(value, replacer, resolve) {
507 |   var
508 |     doNotIgnore = false,
509 |     inspect = !!replacer,
510 |     path = [],
511 |     all  = [value],
512 |     seen = [value],
513 |     mapp = [resolve ? specialChar : '[Circular]'],
514 |     last = value,
515 |     lvl  = 1,
516 |     i, fn
517 |   ;
518 |   if (inspect) {
519 |     fn = typeof replacer === 'object' ?
520 |       function (key, value) {
521 |         return key !== '' && replacer.indexOf(key) < 0 ? void 0 : value;
522 |       } :
523 |       replacer;
524 |   }
525 |   return function(key, value) {
526 |     // the replacer has rights to decide
527 |     // if a new object should be returned
528 |     // or if there's some key to drop
529 |     // let's call it here rather than "too late"
530 |     if (inspect) value = fn.call(this, key, value);
531 |  
532 |     // first pass should be ignored, since it's just the initial object
533 |     if (doNotIgnore) {
534 |       if (last !== this) {
535 |         i = lvl - indexOf.call(all, this) - 1;
536 |         lvl -= i;
537 |         all.splice(lvl, all.length);
538 |         path.splice(lvl - 1, path.length);
539 |         last = this;
540 |       }
541 |       // console.log(lvl, key, path);
542 |       if (typeof value === 'object' && value) {
543 |     	// if object isn't referring to parent object, add to the
544 |         // object path stack. Otherwise it is already there.
545 |         if (indexOf.call(all, value) < 0) {
546 |           all.push(last = value);
547 |         }
548 |         lvl = all.length;
549 |         i = indexOf.call(seen, value);
550 |         if (i < 0) {
551 |           i = seen.push(value) - 1;
552 |           if (resolve) {
553 |             // key cannot contain specialChar but could be not a string
554 |             path.push(('' + key).replace(specialCharRG, safeSpecialChar));
555 |             mapp[i] = specialChar + path.join(specialChar);
556 |           } else {
557 |             mapp[i] = mapp[0];
558 |           }
559 |         } else {
560 |           value = mapp[i];
561 |         }
562 |       } else {
563 |         if (typeof value === 'string' && resolve) {
564 |           // ensure no special char involved on deserialization
565 |           // in this case only first char is important
566 |           // no need to replace all value (better performance)
567 |           value = value .replace(safeSpecialChar, escapedSafeSpecialChar)
568 |                         .replace(specialChar, safeSpecialChar);
569 |         }
570 |       }
571 |     } else {
572 |       doNotIgnore = true;
573 |     }
574 |     return value;
575 |   };
576 | }
577 |  
578 | function retrieveFromPath(current, keys) {
579 |   for(var i = 0, length = keys.length; i < length; current = current[
580 |     // keys should be normalized back here
581 |     keys[i++].replace(safeSpecialCharRG, specialChar)
582 |   ]);
583 |   return current;
584 | }
585 |  
586 | function generateReviver(reviver) {
587 |   return function(key, value) {
588 |     var isString = typeof value === 'string';
589 |     if (isString && value.charAt(0) === specialChar) {
590 |       return new $String(value.slice(1));
591 |     }
592 |     if (key === '') value = regenerate(value, value, {});
593 |     // again, only one needed, do not use the RegExp for this replacement
594 |     // only keys need the RegExp
595 |     if (isString) value = value .replace(safeStartWithSpecialCharRG, '$1' + specialChar)
596 |                                 .replace(escapedSafeSpecialChar, safeSpecialChar);
597 |     return reviver ? reviver.call(this, key, value) : value;
598 |   };
599 | }
600 |  
601 | function regenerateArray(root, current, retrieve) {
602 |   for (var i = 0, length = current.length; i < length; i++) {
603 |     current[i] = regenerate(root, current[i], retrieve);
604 |   }
605 |   return current;
606 | }
607 |  
608 | function regenerateObject(root, current, retrieve) {
609 |   for (var key in current) {
610 |     if (current.hasOwnProperty(key)) {
611 |       current[key] = regenerate(root, current[key], retrieve);
612 |     }
613 |   }
614 |   return current;
615 | }
616 |  
617 | function regenerate(root, current, retrieve) {
618 |   return current instanceof Array ?
619 |     // fast Array reconstruction
620 |     regenerateArray(root, current, retrieve) :
621 |     (
622 |       current instanceof $String ?
623 |         (
624 |           // root is an empty string
625 |           current.length ?
626 |             (
627 |               retrieve.hasOwnProperty(current) ?
628 |                 retrieve[current] :
629 |                 retrieve[current] = retrieveFromPath(
630 |                   root, current.split(specialChar)
631 |                 )
632 |             ) :
633 |             root
634 |         ) :
635 |         (
636 |           current instanceof Object ?
637 |             // dedicated Object parser
638 |             regenerateObject(root, current, retrieve) :
639 |             // value as it is
640 |             current
641 |         )
642 |     )
643 |   ;
644 | }
645 |  
646 | var CircularJSON = {
647 |   stringify: function stringify(value, replacer, space, doNotResolve) {
648 |     return CircularJSON.parser.stringify(
649 |       value,
650 |       generateReplacer(value, replacer, !doNotResolve),
651 |       space
652 |     );
653 |   },
654 |   parse: function parse(text, reviver) {
655 |     return CircularJSON.parser.parse(
656 |       text,
657 |       generateReviver(reviver)
658 |     );
659 |   },
660 |   // A parser should be an API 1:1 compatible with JSON
661 |   // it should expose stringify and parse methods.
662 |   // The default parser is the native JSON.
663 |   parser: JSON
664 | };
665 |  
666 | module.exports = CircularJSON;
667 |  
669 |
670 |
671 | 675 | 676 | 677 | 684 | 685 | 686 | 687 | -------------------------------------------------------------------------------- /coverage/lcov-report/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for build/ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files build/ 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 62/62 26 |
27 |
28 | 100% 29 | Branches 30 | 56/56 31 |
32 |
33 | 100% 34 | Functions 35 | 12/12 36 |
37 |
38 | 100% 39 | Lines 40 | 59/59 41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
FileStatementsBranchesFunctionsLines
circular-json.node.js
100%62/62100%56/56100%12/12100%59/59
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | / 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 62/62 26 |
27 |
28 | 100% 29 | Branches 30 | 56/56 31 |
32 |
33 | 100% 34 | Functions 35 | 12/12 36 |
37 |
38 | 100% 39 | Lines 40 | 59/59 41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
FileStatementsBranchesFunctionsLines
build/
100%62/62100%56/56100%12/12100%59/59
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.js: -------------------------------------------------------------------------------- 1 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/circular-json/af2987de75dfe78c68546584e1594f0c18f9838b/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:/Users/agiammarchi/git/circular-json/build/circular-json.node.js 3 | FN:38,(anonymous_1) 4 | FN:47,generateReplacer 5 | FN:61,(anonymous_3) 6 | FN:66,(anonymous_4) 7 | FN:119,retrieveFromPath 8 | FN:127,generateReviver 9 | FN:128,(anonymous_7) 10 | FN:142,regenerateArray 11 | FN:149,regenerateObject 12 | FN:158,regenerate 13 | FN:188,stringify 14 | FN:195,parse 15 | FNF:12 16 | FNH:12 17 | FNDA:153,(anonymous_1) 18 | FNDA:20,generateReplacer 19 | FNDA:5,(anonymous_3) 20 | FNDA:133,(anonymous_4) 21 | FNDA:11,retrieveFromPath 22 | FNDA:15,generateReviver 23 | FNDA:112,(anonymous_7) 24 | FNDA:9,regenerateArray 25 | FNDA:42,regenerateObject 26 | FNDA:115,regenerate 27 | FNDA:20,stringify 28 | FNDA:15,parse 29 | DA:23,1 30 | DA:39,153 31 | DA:40,153 32 | DA:47,1 33 | DA:48,20 34 | DA:59,20 35 | DA:60,4 36 | DA:62,5 37 | DA:66,20 38 | DA:71,133 39 | DA:74,133 40 | DA:75,113 41 | DA:76,25 42 | DA:77,25 43 | DA:78,25 44 | DA:79,25 45 | DA:80,25 46 | DA:83,113 47 | DA:86,64 48 | DA:87,48 49 | DA:89,64 50 | DA:90,64 51 | DA:91,64 52 | DA:92,36 53 | DA:93,36 54 | DA:95,35 55 | DA:96,35 56 | DA:98,1 57 | DA:101,28 58 | DA:104,49 59 | DA:108,21 60 | DA:113,20 61 | DA:115,133 62 | DA:119,1 63 | DA:120,11 64 | DA:124,11 65 | DA:127,1 66 | DA:128,15 67 | DA:129,112 68 | DA:130,112 69 | DA:131,25 70 | DA:133,87 71 | DA:136,87 72 | DA:138,87 73 | DA:142,1 74 | DA:143,9 75 | DA:144,16 76 | DA:146,9 77 | DA:149,1 78 | DA:150,42 79 | DA:151,86 80 | DA:152,83 81 | DA:155,42 82 | DA:158,1 83 | DA:159,115 84 | DA:187,1 85 | DA:189,20 86 | DA:196,15 87 | DA:207,1 88 | LF:59 89 | LH:59 90 | BRDA:38,1,0,1 91 | BRDA:38,1,1,1 92 | BRDA:39,2,0,448 93 | BRDA:39,2,1,364 94 | BRDA:54,3,0,19 95 | BRDA:54,3,1,1 96 | BRDA:59,4,0,4 97 | BRDA:59,4,1,16 98 | BRDA:60,5,0,1 99 | BRDA:60,5,1,3 100 | BRDA:62,6,0,2 101 | BRDA:62,6,1,3 102 | BRDA:62,7,0,5 103 | BRDA:62,7,1,4 104 | BRDA:71,8,0,15 105 | BRDA:71,8,1,118 106 | BRDA:74,9,0,113 107 | BRDA:74,9,1,20 108 | BRDA:75,10,0,25 109 | BRDA:75,10,1,88 110 | BRDA:83,11,0,64 111 | BRDA:83,11,1,49 112 | BRDA:83,12,0,113 113 | BRDA:83,12,1,64 114 | BRDA:86,13,0,48 115 | BRDA:86,13,1,16 116 | BRDA:91,14,0,36 117 | BRDA:91,14,1,28 118 | BRDA:93,15,0,35 119 | BRDA:93,15,1,1 120 | BRDA:104,16,0,21 121 | BRDA:104,16,1,28 122 | BRDA:104,17,0,49 123 | BRDA:104,17,1,21 124 | BRDA:130,18,0,25 125 | BRDA:130,18,1,87 126 | BRDA:130,19,0,112 127 | BRDA:130,19,1,48 128 | BRDA:133,20,0,16 129 | BRDA:133,20,1,71 130 | BRDA:136,21,0,23 131 | BRDA:136,21,1,64 132 | BRDA:138,22,0,3 133 | BRDA:138,22,1,84 134 | BRDA:151,23,0,83 135 | BRDA:151,23,1,3 136 | BRDA:159,24,0,9 137 | BRDA:159,24,1,106 138 | BRDA:163,25,0,25 139 | BRDA:163,25,1,81 140 | BRDA:166,26,0,17 141 | BRDA:166,26,1,8 142 | BRDA:168,27,0,6 143 | BRDA:168,27,1,11 144 | BRDA:177,28,0,42 145 | BRDA:177,28,1,39 146 | BRF:56 147 | BRH:56 148 | end_of_record 149 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 |

results in console

15 |
green background: all good
16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.5.9", 3 | "name": "circular-json", 4 | "description": "JSON does not handle circular references. This version does", 5 | "license": "MIT", 6 | "homepage": "https://github.com/WebReflection/circular-json", 7 | "keywords": [ 8 | "JSON", 9 | "circular", 10 | "reference", 11 | "recursive", 12 | "recursion", 13 | "parse", 14 | "stringify" 15 | ], 16 | "generator": "https://github.com/WebReflection/gitstrap", 17 | "author": { 18 | "name": "Andrea Giammarchi", 19 | "web": "http://webreflection.blogspot.com/" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/WebReflection/circular-json.git" 24 | }, 25 | "unpkg": "build/circular-json.js", 26 | "main": "./build/circular-json.node.js", 27 | "scripts": { 28 | "test": "istanbul cover test/circular-json.js", 29 | "coveralls": "cat ./coverage/lcov.info | coveralls", 30 | "web": "$(sleep 2 && open http://0.0.0.0:7151/) & tiny-cdn run ./" 31 | }, 32 | "devDependencies": { 33 | "coveralls": "^2.13.0", 34 | "istanbul": "^0.4.5", 35 | "tiny-cdn": "^0.7.0", 36 | "tressa": "^0.3.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/circular-json.js: -------------------------------------------------------------------------------- 1 | var 2 | // should be a not so common char 3 | // possibly one JSON does not encode 4 | // possibly one encodeURIComponent does not encode 5 | // right now this char is '~' but this might change in the future 6 | specialChar = '~', 7 | safeSpecialChar = '\\x' + ( 8 | '0' + specialChar.charCodeAt(0).toString(16) 9 | ).slice(-2), 10 | escapedSafeSpecialChar = '\\' + safeSpecialChar, 11 | specialCharRG = new RegExp(safeSpecialChar, 'g'), 12 | safeSpecialCharRG = new RegExp(escapedSafeSpecialChar, 'g'), 13 | 14 | safeStartWithSpecialCharRG = new RegExp('(?:^|([^\\\\]))' + escapedSafeSpecialChar), 15 | 16 | indexOf = [].indexOf || function(v){ 17 | for(var i=this.length;i--&&this[i]!==v;); 18 | return i; 19 | }, 20 | $String = String // there's no way to drop warnings in JSHint 21 | // about new String ... well, I need that here! 22 | // faked, and happy linter! 23 | ; 24 | 25 | function generateReplacer(value, replacer, resolve) { 26 | var 27 | doNotIgnore = false, 28 | inspect = !!replacer, 29 | path = [], 30 | all = [value], 31 | seen = [value], 32 | mapp = [resolve ? specialChar : '[Circular]'], 33 | last = value, 34 | lvl = 1, 35 | i, fn 36 | ; 37 | if (inspect) { 38 | fn = typeof replacer === 'object' ? 39 | function (key, value) { 40 | return key !== '' && indexOf.call(replacer, key) < 0 ? void 0 : value; 41 | } : 42 | replacer; 43 | } 44 | return function(key, value) { 45 | // the replacer has rights to decide 46 | // if a new object should be returned 47 | // or if there's some key to drop 48 | // let's call it here rather than "too late" 49 | if (inspect) value = fn.call(this, key, value); 50 | 51 | // first pass should be ignored, since it's just the initial object 52 | if (doNotIgnore) { 53 | if (last !== this) { 54 | i = lvl - indexOf.call(all, this) - 1; 55 | lvl -= i; 56 | all.splice(lvl, all.length); 57 | path.splice(lvl - 1, path.length); 58 | last = this; 59 | } 60 | // console.log(lvl, key, path); 61 | if (typeof value === 'object' && value) { 62 | // if object isn't referring to parent object, add to the 63 | // object path stack. Otherwise it is already there. 64 | if (indexOf.call(all, value) < 0) { 65 | all.push(last = value); 66 | } 67 | lvl = all.length; 68 | i = indexOf.call(seen, value); 69 | if (i < 0) { 70 | i = seen.push(value) - 1; 71 | if (resolve) { 72 | // key cannot contain specialChar but could be not a string 73 | path.push(('' + key).replace(specialCharRG, safeSpecialChar)); 74 | mapp[i] = specialChar + path.join(specialChar); 75 | } else { 76 | mapp[i] = mapp[0]; 77 | } 78 | } else { 79 | value = mapp[i]; 80 | } 81 | } else { 82 | if (typeof value === 'string' && resolve) { 83 | // ensure no special char involved on deserialization 84 | // in this case only first char is important 85 | // no need to replace all value (better performance) 86 | value = value .replace(safeSpecialChar, escapedSafeSpecialChar) 87 | .replace(specialChar, safeSpecialChar); 88 | } 89 | } 90 | } else { 91 | doNotIgnore = true; 92 | } 93 | return value; 94 | }; 95 | } 96 | 97 | function retrieveFromPath(current, keys) { 98 | for(var i = 0, length = keys.length; i < length; current = current[ 99 | // keys should be normalized back here 100 | keys[i++].replace(safeSpecialCharRG, specialChar) 101 | ]); 102 | return current; 103 | } 104 | 105 | function generateReviver(reviver) { 106 | return function(key, value) { 107 | var isString = typeof value === 'string'; 108 | if (isString && value.charAt(0) === specialChar) { 109 | return new $String(value.slice(1)); 110 | } 111 | if (key === '') value = regenerate(value, value, {}); 112 | // again, only one needed, do not use the RegExp for this replacement 113 | // only keys need the RegExp 114 | if (isString) value = value .replace(safeStartWithSpecialCharRG, '$1' + specialChar) 115 | .replace(escapedSafeSpecialChar, safeSpecialChar); 116 | return reviver ? reviver.call(this, key, value) : value; 117 | }; 118 | } 119 | 120 | function regenerateArray(root, current, retrieve) { 121 | for (var i = 0, length = current.length; i < length; i++) { 122 | current[i] = regenerate(root, current[i], retrieve); 123 | } 124 | return current; 125 | } 126 | 127 | function regenerateObject(root, current, retrieve) { 128 | for (var key in current) { 129 | if (current.hasOwnProperty(key)) { 130 | current[key] = regenerate(root, current[key], retrieve); 131 | } 132 | } 133 | return current; 134 | } 135 | 136 | function regenerate(root, current, retrieve) { 137 | return current instanceof Array ? 138 | // fast Array reconstruction 139 | regenerateArray(root, current, retrieve) : 140 | ( 141 | current instanceof $String ? 142 | ( 143 | // root is an empty string 144 | current.length ? 145 | ( 146 | retrieve.hasOwnProperty(current) ? 147 | retrieve[current] : 148 | retrieve[current] = retrieveFromPath( 149 | root, current.split(specialChar) 150 | ) 151 | ) : 152 | root 153 | ) : 154 | ( 155 | current instanceof Object ? 156 | // dedicated Object parser 157 | regenerateObject(root, current, retrieve) : 158 | // value as it is 159 | current 160 | ) 161 | ) 162 | ; 163 | } 164 | 165 | var CircularJSON = { 166 | stringify: function stringify(value, replacer, space, doNotResolve) { 167 | return CircularJSON.parser.stringify( 168 | value, 169 | generateReplacer(value, replacer, !doNotResolve), 170 | space 171 | ); 172 | }, 173 | parse: function parse(text, reviver) { 174 | return CircularJSON.parser.parse( 175 | text, 176 | generateReviver(reviver) 177 | ); 178 | }, 179 | // A parser should be an API 1:1 compatible with JSON 180 | // it should expose stringify and parse methods. 181 | // The default parser is the native JSON. 182 | parser: JSON 183 | }; 184 | -------------------------------------------------------------------------------- /template/amd.after: -------------------------------------------------------------------------------- 1 | 2 | return CircularJSON; 3 | 4 | }(JSON, RegExp))); -------------------------------------------------------------------------------- /template/amd.before: -------------------------------------------------------------------------------- 1 | define((function(JSON, RegExp){ 2 | -------------------------------------------------------------------------------- /template/copyright: -------------------------------------------------------------------------------- 1 | /*! (C) WebReflection Mit Style License */ 2 | -------------------------------------------------------------------------------- /template/license.after: -------------------------------------------------------------------------------- 1 | 2 | */ 3 | -------------------------------------------------------------------------------- /template/license.before: -------------------------------------------------------------------------------- 1 | /*! 2 | -------------------------------------------------------------------------------- /template/md.after: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /template/md.before: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 31 | 32 | -------------------------------------------------------------------------------- /template/node.after: -------------------------------------------------------------------------------- 1 | 2 | module.exports = CircularJSON; 3 | -------------------------------------------------------------------------------- /template/node.before: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/circular-json/af2987de75dfe78c68546584e1594f0c18f9838b/template/node.before -------------------------------------------------------------------------------- /template/var.after: -------------------------------------------------------------------------------- 1 | 2 | return CircularJSON; 3 | 4 | }(JSON, RegExp)); -------------------------------------------------------------------------------- /template/var.before: -------------------------------------------------------------------------------- 1 | var CircularJSON = (function(JSON, RegExp){ 2 | -------------------------------------------------------------------------------- /test/benchmark.js: -------------------------------------------------------------------------------- 1 | var dummy100 = [{"id":1,"sex":"Female","age":38,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"1st 2nd 3rd or 4th grade","race":"White"},{"id":2,"sex":"Female","age":44,"classOfWorker":"Self-employed-not incorporated","maritalStatus":"Married-civilian spouse present","education":"Associates degree-occup /vocational","race":"White"},{"id":3,"sex":"Male","age":2,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":4,"sex":"Female","age":35,"classOfWorker":"Private","maritalStatus":"Divorced","education":"High school graduate","race":"White"},{"id":5,"sex":"Male","age":49,"classOfWorker":"Private","maritalStatus":"Divorced","education":"High school graduate","race":"White"},{"id":6,"sex":"Male","age":13,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":7,"sex":"Female","age":1,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":8,"sex":"Female","age":61,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":9,"sex":"Male","age":38,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Masters degree(MA MS MEng MEd MSW MBA)","race":"Black"},{"id":10,"sex":"Female","age":7,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":11,"sex":"Female","age":30,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Bachelors degree(BA AB BS)","race":"White"},{"id":12,"sex":"Male","age":85,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"10th grade","race":"White"},{"id":13,"sex":"Female","age":33,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"Asian or Pacific Islander"},{"id":14,"sex":"Male","age":26,"classOfWorker":"Private","maritalStatus":"Never married","education":"Some college but no degree","race":"White"},{"id":15,"sex":"Female","age":46,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":16,"sex":"Female","age":19,"classOfWorker":"Private","maritalStatus":"Never married","education":"Some college but no degree","race":"White"},{"id":17,"sex":"Male","age":11,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":18,"sex":"Male","age":23,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Bachelors degree(BA AB BS)","race":"White"},{"id":19,"sex":"Male","age":27,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":20,"sex":"Male","age":35,"classOfWorker":"Self-employed-not incorporated","maritalStatus":"Divorced","education":"High school graduate","race":"Black"},{"id":21,"sex":"Male","age":8,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":22,"sex":"Female","age":29,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":23,"sex":"Female","age":40,"classOfWorker":"Private","maritalStatus":"Divorced","education":"Some college but no degree","race":"White"},{"id":24,"sex":"Male","age":24,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":25,"sex":"Male","age":45,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Some college but no degree","race":"White"},{"id":26,"sex":"Female","age":27,"classOfWorker":"Private","maritalStatus":"Never married","education":"Some college but no degree","race":"White"},{"id":27,"sex":"Male","age":41,"classOfWorker":"Local government","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":28,"sex":"Female","age":14,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":29,"sex":"Male","age":73,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"7th and 8th grade","race":"White"},{"id":30,"sex":"Male","age":46,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Some college but no degree","race":"White"},{"id":31,"sex":"Female","age":78,"classOfWorker":"Not in universe","maritalStatus":"Widowed","education":"7th and 8th grade","race":"White"},{"id":32,"sex":"Male","age":27,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":33,"sex":"Female","age":81,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"9th grade","race":"White"},{"id":34,"sex":"Male","age":35,"classOfWorker":"Private","maritalStatus":"Never married","education":"Masters degree(MA MS MEng MEd MSW MBA)","race":"White"},{"id":35,"sex":"Female","age":15,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"7th and 8th grade","race":"White"},{"id":36,"sex":"Female","age":27,"classOfWorker":"State government","maritalStatus":"Never married","education":"12th grade no diploma","race":"Black"},{"id":37,"sex":"Male","age":68,"classOfWorker":"Not in universe","maritalStatus":"Widowed","education":"Less than 1st grade","race":"Other"},{"id":38,"sex":"Female","age":28,"classOfWorker":"Private","maritalStatus":"Never married","education":"11th grade","race":"Black"},{"id":39,"sex":"Male","age":54,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Masters degree(MA MS MEng MEd MSW MBA)","race":"White"},{"id":40,"sex":"Female","age":37,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":41,"sex":"Male","age":82,"classOfWorker":"Not in universe","maritalStatus":"Widowed","education":"7th and 8th grade","race":"White"},{"id":42,"sex":"Female","age":55,"classOfWorker":"Self-employed-not incorporated","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"Asian or Pacific Islander"},{"id":43,"sex":"Male","age":77,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"Less than 1st grade","race":"White"},{"id":44,"sex":"Male","age":53,"classOfWorker":"Private","maritalStatus":"Divorced","education":"Some college but no degree","race":"White"},{"id":45,"sex":"Male","age":25,"classOfWorker":"Private","maritalStatus":"Never married","education":"Some college but no degree","race":"White"},{"id":46,"sex":"Male","age":23,"classOfWorker":"Private","maritalStatus":"Never married","education":"Associates degree-academic program","race":"White"},{"id":47,"sex":"Female","age":0,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":48,"sex":"Female","age":49,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Some college but no degree","race":"White"},{"id":49,"sex":"Female","age":75,"classOfWorker":"Not in universe","maritalStatus":"Widowed","education":"10th grade","race":"White"},{"id":50,"sex":"Male","age":80,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"High school graduate","race":"White"},{"id":51,"sex":"Female","age":10,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"Asian or Pacific Islander"},{"id":52,"sex":"Male","age":22,"classOfWorker":"State government","maritalStatus":"Never married","education":"Associates degree-occup /vocational","race":"White"},{"id":53,"sex":"Female","age":61,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"9th grade","race":"White"},{"id":54,"sex":"Female","age":1,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":55,"sex":"Female","age":43,"classOfWorker":"Self-employed-not incorporated","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":56,"sex":"Male","age":48,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"Bachelors degree(BA AB BS)","race":"White"},{"id":57,"sex":"Female","age":5,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"Black"},{"id":58,"sex":"Female","age":16,"classOfWorker":"Never worked","maritalStatus":"Never married","education":"10th grade","race":"White"},{"id":59,"sex":"Female","age":27,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"High school graduate","race":"White"},{"id":60,"sex":"Male","age":61,"classOfWorker":"Self-employed-not incorporated","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":61,"sex":"Female","age":18,"classOfWorker":"Private","maritalStatus":"Never married","education":"11th grade","race":"Asian or Pacific Islander"},{"id":62,"sex":"Male","age":54,"classOfWorker":"Local government","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":63,"sex":"Male","age":50,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"7th and 8th grade","race":"White"},{"id":64,"sex":"Female","age":64,"classOfWorker":"Private","maritalStatus":"Widowed","education":"High school graduate","race":"White"},{"id":65,"sex":"Male","age":64,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"1st 2nd 3rd or 4th grade","race":"Black"},{"id":66,"sex":"Male","age":3,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":67,"sex":"Female","age":45,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"11th grade","race":"White"},{"id":68,"sex":"Female","age":72,"classOfWorker":"Not in universe","maritalStatus":"Widowed","education":"Bachelors degree(BA AB BS)","race":"White"},{"id":69,"sex":"Male","age":80,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":70,"sex":"Male","age":47,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Bachelors degree(BA AB BS)","race":"Black"},{"id":71,"sex":"Female","age":39,"classOfWorker":"Private","maritalStatus":"Divorced","education":"Associates degree-occup /vocational","race":"White"},{"id":72,"sex":"Male","age":51,"classOfWorker":"Self-employed-not incorporated","maritalStatus":"Married-civilian spouse present","education":"Some college but no degree","race":"White"},{"id":73,"sex":"Male","age":12,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"Asian or Pacific Islander"},{"id":74,"sex":"Male","age":41,"classOfWorker":"Private","maritalStatus":"Never married","education":"Some college but no degree","race":"Asian or Pacific Islander"},{"id":75,"sex":"Male","age":39,"classOfWorker":"Local government","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":76,"sex":"Female","age":67,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":77,"sex":"Female","age":59,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":78,"sex":"Female","age":48,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"10th grade","race":"White"},{"id":79,"sex":"Female","age":42,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"7th and 8th grade","race":"White"},{"id":80,"sex":"Male","age":38,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Some college but no degree","race":"White"},{"id":81,"sex":"Female","age":28,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Masters degree(MA MS MEng MEd MSW MBA)","race":"White"},{"id":82,"sex":"Male","age":8,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":83,"sex":"Female","age":4,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":84,"sex":"Male","age":26,"classOfWorker":"Private","maritalStatus":"Never married","education":"Some college but no degree","race":"White"},{"id":85,"sex":"Female","age":55,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"Associates degree-occup /vocational","race":"White"},{"id":86,"sex":"Female","age":42,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":87,"sex":"Male","age":18,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"12th grade no diploma","race":"White"},{"id":88,"sex":"Female","age":14,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":89,"sex":"Male","age":4,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":90,"sex":"Female","age":32,"classOfWorker":"Private","maritalStatus":"Never married","education":"Some college but no degree","race":"Black"},{"id":91,"sex":"Female","age":83,"classOfWorker":"Not in universe","maritalStatus":"Widowed","education":"7th and 8th grade","race":"White"},{"id":92,"sex":"Female","age":5,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":93,"sex":"Female","age":33,"classOfWorker":"Not in universe","maritalStatus":"Married-civilian spouse present","education":"Bachelors degree(BA AB BS)","race":"White"},{"id":94,"sex":"Female","age":57,"classOfWorker":"Self-employed-incorporated","maritalStatus":"Widowed","education":"12th grade no diploma","race":"White"},{"id":95,"sex":"Female","age":6,"classOfWorker":"Not in universe","maritalStatus":"Never married","education":"Children","race":"White"},{"id":96,"sex":"Female","age":37,"classOfWorker":"Local government","maritalStatus":"Married-civilian spouse present","education":"Bachelors degree(BA AB BS)","race":"White"},{"id":97,"sex":"Female","age":47,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":98,"sex":"Female","age":54,"classOfWorker":"Not in universe","maritalStatus":"Divorced","education":"9th grade","race":"White"},{"id":99,"sex":"Female","age":55,"classOfWorker":"Private","maritalStatus":"Married-civilian spouse present","education":"High school graduate","race":"White"},{"id":100,"sex":"Female","age":44,"classOfWorker":"Private","maritalStatus":"Divorced","education":"Some college but no degree","race":"White"}]; 2 | var dummy50 = dummy100.slice(0, 50); 3 | var dummy10 = dummy100.slice(0, 10); 4 | 5 | function bench(method, dummy) { 6 | var t = Date.now(), i = 0; 7 | while ((Date.now() - t) < 1000) { 8 | r = method(dummy); 9 | i++; 10 | } 11 | return i; 12 | } 13 | 14 | var r, CircularJSON = require('../build/circular-json.node.js'); 15 | 16 | function run(fn, dummy) { 17 | console.log( 18 | fn.name + ' ' + ( 19 | typeof dummy === 'string' ? 20 | dummy.length + ' chars' : 21 | dummy.length + ' objects' 22 | ) + ' parsed ' + ( 23 | bench(fn, dummy) / 1000 24 | ).toFixed(2) + ' times per second' 25 | ); 26 | } 27 | 28 | console.log('-----------------------------------'); 29 | console.log('Object with ' + Object.keys(dummy100[0]).length + ' keys each'); 30 | console.log('-----------------------------------'); 31 | run(CircularJSON.stringify, dummy100); 32 | run(CircularJSON.parse, r); 33 | run(CircularJSON.stringify, dummy50); 34 | run(CircularJSON.parse, r); 35 | run(CircularJSON.stringify, dummy10); 36 | run(CircularJSON.parse, r); 37 | console.log('-----------------------------------'); 38 | run(JSON.stringify, dummy100); 39 | run(JSON.parse, r); 40 | run(JSON.stringify, dummy50); 41 | run(JSON.parse, r); 42 | run(JSON.stringify, dummy10); 43 | run(JSON.parse, r); 44 | console.log('-----------------------------------'); 45 | console.log('50% same objects'); 46 | dummy100 = dummy50.concat(dummy50); 47 | console.log('-----------------------------------'); 48 | run(CircularJSON.stringify, dummy100); 49 | run(CircularJSON.parse, r); 50 | run(JSON.stringify, dummy100); 51 | run(JSON.parse, r); 52 | console.log('-----------------------------------'); 53 | console.log('90% same objects'); 54 | dummy100 = [].concat( 55 | dummy10, dummy10, dummy10, dummy10, dummy10, 56 | dummy10, dummy10, dummy10, dummy10, dummy10 57 | ); 58 | console.log('-----------------------------------'); 59 | run(CircularJSON.stringify, dummy100); 60 | run(CircularJSON.parse, r); 61 | run(JSON.stringify, dummy100); 62 | run(JSON.parse, r); -------------------------------------------------------------------------------- /test/circular-json.js: -------------------------------------------------------------------------------- 1 | var tressa = tressa || require('tressa'); 2 | 3 | // running for 100% code coverage 4 | var indexOf = Array.prototype.indexOf; 5 | delete Array.prototype.indexOf; 6 | var CircularJSON = CircularJSON || require('../build/circular-json.node.js'); 7 | Array.prototype.indexOf = indexOf; 8 | 9 | tressa.title('CircularJSON'); 10 | 11 | (function () { 12 | var special = "\\x7e"; // \x7e is ~ 13 | //tressa.log(CircularJSON.stringify({a:special})); 14 | //tressa.log(CircularJSON.parse(CircularJSON.stringify({a:special})).a); 15 | tressa.assert(CircularJSON.parse(CircularJSON.stringify({a:special})).a === special, 'no problem with simulation'); 16 | special = "~\\x7e"; 17 | tressa.assert(CircularJSON.parse(CircularJSON.stringify({a:special})).a === special, 'no problem with special char'); 18 | }()); 19 | 20 | (function () { 21 | var o = {a: 'a', b: 'b', c: function(){}, d: {e: 123}}, 22 | a, b; 23 | tressa.assert( 24 | (a = JSON.stringify(o)) === (b = CircularJSON.stringify(o)), 25 | 'works as JSON.stringify' 26 | ); 27 | tressa.assert( 28 | JSON.stringify(JSON.parse(a)) === JSON.stringify(CircularJSON.parse(b)), 29 | 'works as JSON.parse' 30 | ); 31 | tressa.assert( 32 | CircularJSON.stringify(o, function(key, value){ 33 | if (!key || key === 'a') return value; 34 | }) === '{"a":"a"}', 35 | 'accept callback' 36 | ); 37 | tressa.assert( 38 | JSON.stringify( 39 | CircularJSON.parse('{"a":"a"}', function(key, value){ 40 | if (key === 'a') return 'b'; 41 | return value; 42 | }) 43 | ) === '{"a":"b"}', 44 | 'revive callback' 45 | ); 46 | }()); 47 | 48 | (function () { 49 | var o = {}, before, after; 50 | o.a = o; 51 | o.c = {}; 52 | o.d = { 53 | a: 123, 54 | b: o 55 | }; 56 | o.c.e = o; 57 | o.c.f = o.d; 58 | o.b = o.c; 59 | before = CircularJSON.stringify(o); 60 | o = CircularJSON.parse(before); 61 | tressa.assert( 62 | o.b === o.c && 63 | o.c.e === o && 64 | o.d.a === 123 && 65 | o.d.b === o && 66 | o.c.f === o.d && 67 | o.b === o.c, 68 | 'recreated original structure' 69 | ); 70 | }()); 71 | 72 | (function () { 73 | var o = {}; 74 | o.a = o; 75 | o.b = o; 76 | tressa.assert( 77 | CircularJSON.stringify(o, function (key, value) { 78 | if (!key || key === 'a') return value; 79 | }) === '{"a":"~"}', 80 | 'callback invoked' 81 | ); 82 | o = CircularJSON.parse('{"a":"~"}', function (key, value) { 83 | if (!key) { 84 | value.b = value; 85 | } 86 | return value; 87 | }); 88 | tressa.assert( 89 | o.a === o && o.b === o, 90 | 'reviver invoked' 91 | ); 92 | }()); 93 | 94 | (function () { 95 | var o = {}; 96 | o['~'] = o; 97 | o['\\x7e'] = '\\x7e'; 98 | o.test = '~'; 99 | 100 | o = CircularJSON.parse(CircularJSON.stringify(o)); 101 | tressa.assert(o['~'] === o && o.test === '~', 'still intact'); 102 | o = { 103 | a: [ 104 | '~', '~~', '~~~' 105 | ] 106 | }; 107 | o.a.push(o); 108 | o.o = o; 109 | o['~'] = o.a; 110 | o['~~'] = o.a; 111 | o['~~~'] = o.a; 112 | o = CircularJSON.parse(CircularJSON.stringify(o)); 113 | tressa.assert( 114 | o === o.a[3] && 115 | o === o.o && 116 | o['~'] === o.a && 117 | o['~~'] === o.a && 118 | o['~~~'] === o.a && 119 | o.a === o.a[3].a && 120 | o.a.pop() === o && 121 | o.a.join('') === '~~~~~~', 122 | 'restructured' 123 | ); 124 | 125 | }()); 126 | 127 | (function () { 128 | var o = {a:[1,2,3]}; 129 | o.o = o; 130 | o.a.push(o); 131 | o = CircularJSON.parse(CircularJSON.stringify(o, null, null, true)); 132 | tressa.assert(o.o === '[Circular]', 'no way to retrieve the path'); 133 | tressa.assert(o.a[3] === '[Circular]', 'same structure though'); 134 | }()); 135 | 136 | (function () { 137 | var found = false, o = {}; 138 | o.o = o; 139 | CircularJSON.stringify(o, function (key, value) { 140 | if (!found && this === o) { 141 | found = true; 142 | } 143 | return value; 144 | }); 145 | tressa.assert(found); 146 | }()); 147 | 148 | (function () { 149 | 150 | // make sure only own properties are parsed 151 | Object.prototype.shenanigans = true; 152 | 153 | var 154 | item = { 155 | name: 'TEST' 156 | }, 157 | original = { 158 | outer: [ 159 | { 160 | a: 'b', 161 | c: 'd', 162 | one: item, 163 | many: [item], 164 | e: 'f' 165 | } 166 | ] 167 | }, 168 | str, 169 | output 170 | ; 171 | item.value = item; 172 | str = CircularJSON.stringify(original); 173 | output = CircularJSON.parse(str); 174 | tressa.assert(str === '{"outer":[{"a":"b","c":"d","one":{"name":"TEST","value":"~outer~0~one"},"many":["~outer~0~one"],"e":"f"}]}', 'string is correct'); 175 | tressa.assert( 176 | original.outer[0].one.name === output.outer[0].one.name && 177 | original.outer[0].many[0].name === output.outer[0].many[0].name && 178 | output.outer[0].many[0] === output.outer[0].one, 179 | 'object too' 180 | ); 181 | 182 | delete Object.prototype.shenanigans; 183 | 184 | }()); 185 | 186 | (function () { 187 | var 188 | unique = {a:'sup'}, 189 | nested = { 190 | prop: { 191 | value: 123 192 | }, 193 | a: [ 194 | {}, 195 | {b: [ 196 | { 197 | a: 1, 198 | d: 2, 199 | c: unique, 200 | z: { 201 | g: 2, 202 | a: unique, 203 | b: { 204 | r: 4, 205 | u: unique, 206 | c: 5 207 | }, 208 | f: 6 209 | }, 210 | h: 1 211 | } 212 | ]} 213 | ], 214 | b: { 215 | e: 'f', 216 | t: unique, 217 | p: 4 218 | } 219 | }, 220 | str = CircularJSON.stringify(nested), 221 | output 222 | ; 223 | tressa.assert(str === '{"prop":{"value":123},"a":[{},{"b":[{"a":1,"d":2,"c":{"a":"sup"},"z":{"g":2,"a":"~a~1~b~0~c","b":{"r":4,"u":"~a~1~b~0~c","c":5},"f":6},"h":1}]}],"b":{"e":"f","t":"~a~1~b~0~c","p":4}}', 'string is OK'); 224 | output = CircularJSON.parse(str); 225 | tressa.assert(output.b.t.a === 'sup' && output.a[1].b[0].c === output.b.t, 'so is the object'); 226 | }()); 227 | 228 | (function () { 229 | var o = {bar: 'something ~ baz'}; 230 | var s = CircularJSON.stringify(o); 231 | tressa.assert(s === '{"bar":"something \\\\x7e baz"}', 'string is correct'); 232 | var oo = CircularJSON.parse(s); 233 | tressa.assert(oo.bar === o.bar, 'parse is correct'); 234 | }()); 235 | 236 | (function () { 237 | var o = {}; 238 | o.a = { 239 | aa: { 240 | aaa: 'value1' 241 | } 242 | }; 243 | o.b = o; 244 | o.c = { 245 | ca: {}, 246 | cb: {}, 247 | cc: {}, 248 | cd: {}, 249 | ce: 'value2', 250 | cf: 'value3' 251 | }; 252 | o.c.ca.caa = o.c.ca; 253 | o.c.cb.cba = o.c.cb; 254 | o.c.cc.cca = o.c; 255 | o.c.cd.cda = o.c.ca.caa; 256 | 257 | var s = CircularJSON.stringify(o); 258 | tressa.assert(s === '{"a":{"aa":{"aaa":"value1"}},"b":"~","c":{"ca":{"caa":"~c~ca"},"cb":{"cba":"~c~cb"},"cc":{"cca":"~c"},"cd":{"cda":"~c~ca"},"ce":"value2","cf":"value3"}}', 'string is correct'); 259 | var oo = CircularJSON.parse(s); 260 | tressa.assert( 261 | oo.a.aa.aaa = 'value1' 262 | && oo === oo.b 263 | && o.c.ca.caa === o.c.ca 264 | && o.c.cb.cba === o.c.cb 265 | && o.c.cc.cca === o.c 266 | && o.c.cd.cda === o.c.ca.caa 267 | && oo.c.ce === 'value2' 268 | && oo.c.cf === 'value3', 269 | 'parse is correct' 270 | ); 271 | }()); 272 | 273 | (function () { 274 | var 275 | original = { 276 | a1: { 277 | a2: [], 278 | a3: [{name: 'whatever'}] 279 | }, 280 | a4: [] 281 | }, 282 | json, 283 | restored 284 | ; 285 | 286 | original.a1.a2[0] = original.a1; 287 | original.a4[0] = original.a1.a3[0]; 288 | 289 | json = CircularJSON.stringify(original); 290 | restored = CircularJSON.parse(json); 291 | 292 | tressa.assert(restored.a1.a2[0] === restored.a1, '~a1~a2~0 === ~a1'); 293 | tressa.assert(restored.a4[0] = restored.a1.a3[0], '~a4 === ~a1~a3~0'); 294 | }()); 295 | 296 | if (typeof Symbol !== 'undefined') { 297 | (function () { 298 | var o = {a: 1}; 299 | var a = [1, Symbol('test'), 2]; 300 | o[Symbol('test')] = 123; 301 | tressa.assert(JSON.stringify(o) === CircularJSON.stringify(o), 'Symbol is OK too'); 302 | tressa.assert(JSON.stringify(a) === CircularJSON.stringify(a)); 303 | }()); 304 | } 305 | 306 | (function () { 307 | var args = [{a:[1]}, null, ' ']; 308 | tressa.assert(CircularJSON.stringify.apply(null, args) === JSON.stringify.apply(null, args), 'extra args same as JSON'); 309 | }()); 310 | 311 | (function () { 312 | var o = {a: 1, b: {a: 1, b: 2}}; 313 | var json = JSON.stringify(o, ['b']); 314 | tressa.assert( 315 | CircularJSON.stringify(o, ['b']) === json, 316 | 'whitelisted ["b"]: '+ json 317 | ); 318 | }()); 319 | 320 | (function () { 321 | var a = { b: { '': { c: { d: 1 } } } }; 322 | a._circular = a.b['']; 323 | var json = CircularJSON.stringify(a); 324 | var nosj = CircularJSON.parse(json); 325 | tressa.assert( 326 | nosj._circular === nosj.b[''] && 327 | JSON.stringify(nosj._circular) === JSON.stringify(a._circular), 328 | 'empty keys as non root objects work' 329 | ); 330 | delete a._circular; 331 | delete nosj._circular; 332 | tressa.assert( 333 | JSON.stringify(nosj) === JSON.stringify(a), 334 | 'objects copied with circular empty keys are the same' 335 | ); 336 | }()); 337 | 338 | if (!tressa.exitCode && typeof document !== 'undefined') { 339 | document.body.style.backgroundColor = '#0FA'; 340 | } 341 | tressa.end(); --------------------------------------------------------------------------------