Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit with tests

  • Loading branch information...
commit c9c3e3a459e5145b82ed003ef3cde6595ebe59da 1 parent 8c3b2e1
@WebReflection authored
View
3  Makefile
@@ -12,6 +12,7 @@ NODE = $(VAR)
# make amd files
AMD = $(VAR)
+
# README constant
@@ -101,6 +102,6 @@ dependencies:
mkdir node_modules
npm install wru
npm install polpetta
+ npm install markdown
npm install uglify-js@1
npm install jshint
- npm install markdown
View
86 README.md
@@ -1,5 +1,87 @@
-circular-json
-=============
+CircularJSON
+============
[![build status](https://secure.travis-ci.org/WebReflection/circular-json.png)](http://travis-ci.org/WebReflection/circular-json)
+
+### A Working Solution To A Common Problem
+A usage example:
+
+```JavaScript
+var object = {};
+object.arr = [
+ object, object
+];
+object.arr.push(object.arr);
+object.obj = object;
+
+var serialized = CircularJSON.stringify(object);
+// '{"arr":["~","~","~arr"],"obj":"~"}'
+
+var unserialized = CircularJSON.parse(serialized);
+// { arr: [ [Circular], [Circular] ],
+// obj: [Circular] }
+
+unserialized.obj === unserialized;
+unserialized.arr[0] === unserialized;
+unserialized.arr.pop() === unserialized.arr;
+```
+
+A quick summary:
+
+ * same as `JSON.stringify` and `JSON.parse` methods with same type of arguments (same JSON API)
+ * reasonably fast in both serialization and deserialization
+ * compact serialization for easier and slimmer transportation across environments
+ * [tested and covered](test/circular-json.js) over nasty structures too
+ * compatible with all JavaScript engines
+
+
+### Dependencies
+A proper **JSON** object must be globally available if the browser/engine does not support it.
+
+Dependencies free if you target IE8 and greater or any server side JS engine.
+
+Bear in mind `JSON.parse(CircularJSON.stringify(object))` will work but not produce the expected output.
+
+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.
+
+As summary: `CircularJSON.parse(CircularJSON.stringify(object))` is the way to go, same is for `JSON.parse(JSON.stringify(object))`.
+
+
+### Which Version
+The usual structure for my repos, the one generated via [gitstrap](https://github.com/WebReflection/gitstrap), so:
+
+ * all browsers, generic, as [global CircularJSON object](build/circular-json.js)
+ * [node.js module](build/circular-json.node.js), also via `npm install circular-json` and later on `var CircularJSON = require('circular-json')`
+ * [AMD module](build/circular-json.amd.js) loader, as CircularJSON object
+
+The **API** is the **same as JSON Object** so nothing new to learn here while [full test coverage](test/circular-json.js) is also in the usual place with some example included.
+
+
+### Why Not the [@izs](https://twitter.com/izs) One
+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:
+
+```JavaScript
+// a logged object with circular references
+{
+ "circularRef": "[Circular]",
+ "list": [
+ "[Circular]",
+ "[Circular]"
+ ]
+}
+// what do we do with above output ?
+```
+
+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.
+
+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.
+
+
+### Why Not {{put random name}} Solution
+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.
+
+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!
+
+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!
+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.
View
2  build/circular-json.amd.js
@@ -1,2 +1,2 @@
/*! (C) WebReflection Mit Style License */
-define({});
+define(function(e,t){function a(e,t){var s=[],u=[e],a=[n],f;return function(e,l){return t&&(l=t(e,l)),e!==""&&(typeof l=="object"&&l?(f=o.call(u,l),f<0?(s.push((""+e).replace(i,r)),a[u.push(l)-1]=n+s.join(n)):l=a[f]):(s.pop(),typeof l=="string"&&(l=l.replace(n,r)))),l}}function f(e,t){for(var r=0,i=t.length;r<i;e=e[t[r++].replace(s,n)]);return e}function l(e){return function(t,i){var s=typeof i=="string";return s&&i.charAt(0)===n?new u(i.slice(1)):(t||(i=p(i,i,{})),s&&(i=i.replace(r,n)),e?e(t,i):i)}}function c(e,t,n){for(var r=0,i=t.length;r<i;r++)t[r]=p(e,t[r],n);return t}function h(e,t,n){for(var r in t)t.hasOwnProperty(r)&&(t[r]=p(e,t[r],n));return t}function p(e,t,r){return t instanceof Array?c(e,t,r):t instanceof u?t.length?r.hasOwnProperty(t)?r[t]:r[t]=f(e,t.split(n)):e:t instanceof Object?h(e,t,r):t}function d(t,n,r){return e.stringify(t,a(t,n),r)}function v(t,n){return e.parse(t,l(n))}var n="~",r="\\x"+("0"+n.charCodeAt(0).toString(16)).slice(-2),i=new t(r,"g"),s=new t("\\"+r,"g"),o=[].indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},u=String;return{stringify:d,parse:v}}(JSON,RegExp));
View
2  build/circular-json.js
@@ -1,2 +1,2 @@
/*! (C) WebReflection Mit Style License */
-var main={};
+var CircularJSON=function(e,t){function a(e,t){var s=[],u=[e],a=[n],f;return function(e,l){return t&&(l=t(e,l)),e!==""&&(typeof l=="object"&&l?(f=o.call(u,l),f<0?(s.push((""+e).replace(i,r)),a[u.push(l)-1]=n+s.join(n)):l=a[f]):(s.pop(),typeof l=="string"&&(l=l.replace(n,r)))),l}}function f(e,t){for(var r=0,i=t.length;r<i;e=e[t[r++].replace(s,n)]);return e}function l(e){return function(t,i){var s=typeof i=="string";return s&&i.charAt(0)===n?new u(i.slice(1)):(t||(i=p(i,i,{})),s&&(i=i.replace(r,n)),e?e(t,i):i)}}function c(e,t,n){for(var r=0,i=t.length;r<i;r++)t[r]=p(e,t[r],n);return t}function h(e,t,n){for(var r in t)t.hasOwnProperty(r)&&(t[r]=p(e,t[r],n));return t}function p(e,t,r){return t instanceof Array?c(e,t,r):t instanceof u?t.length?r.hasOwnProperty(t)?r[t]:r[t]=f(e,t.split(n)):e:t instanceof Object?h(e,t,r):t}function d(t,n,r){return e.stringify(t,a(t,n),r)}function v(t,n){return e.parse(t,l(n))}var n="~",r="\\x"+("0"+n.charCodeAt(0).toString(16)).slice(-2),i=new t(r,"g"),s=new t("\\"+r,"g"),o=[].indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},u=String;return{stringify:d,parse:v}}(JSON,RegExp);
View
140 build/circular-json.max.amd.js
@@ -20,4 +20,142 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
-define({});
+define((function(JSON, RegExp){
+var
+ // should be a not so common char
+ // possibly one JSON does not encode
+ // possibly one encodeURIComponent does not encode
+ // right now this char is '~' but this might change in the future
+ specialChar = '~',
+ safeSpecialChar = '\\x' + (
+ '0' + specialChar.charCodeAt(0).toString(16)
+ ).slice(-2),
+ specialCharRG = new RegExp(safeSpecialChar, 'g'),
+ safeSpecialCharRG = new RegExp('\\' + safeSpecialChar, 'g'),
+ indexOf = [].indexOf || function(v){
+ for(var i=this.length;i--&&this[i]!==v;);
+ return i;
+ },
+ $String = String // there's no way to drop warnings in JSHint
+ // about new String ... well, I need that here!
+ // faked, and happy linter!
+;
+
+function generateReplacer(value, replacer) {
+ var
+ path = [],
+ seen = [value],
+ mapp = [specialChar],
+ i
+ ;
+ return function(key, value) {
+ // the replacer has rights to decide
+ // if a new object should be returned
+ // or if there's some key to drop
+ // let's call it here rather than "too late"
+ if (replacer) value = replacer(key, value);
+
+ // did you know ? Safari passes keys as integers for arrays
+ if (key !== '') {
+ if (typeof value === 'object' && value) {
+ i = indexOf.call(seen, value);
+ if (i < 0) {
+ // key cannot contain specialChar but could be not a string
+ path.push(('' + key).replace(specialCharRG, safeSpecialChar));
+ mapp[seen.push(value) - 1] = specialChar + path.join(specialChar);
+ } else {
+ value = mapp[i];
+ }
+ } else {
+ path.pop();
+ if (typeof value === 'string') {
+ // ensure no special char involved on deserialization
+ // in this case only first char is important
+ // no need to replace all value (better performance)
+ value = value.replace(specialChar, safeSpecialChar);
+ }
+ }
+ }
+ return value;
+ };
+}
+
+function retrieveFromPath(current, keys) {
+ for(var i = 0, length = keys.length; i < length; current = current[
+ // keys should be normalized back here
+ keys[i++].replace(safeSpecialCharRG, specialChar)
+ ]);
+ return current;
+}
+
+function generateReviver(reviver) {
+ return function(key, value) {
+ var isString = typeof value === 'string';
+ if (isString && value.charAt(0) === specialChar) {
+ return new $String(value.slice(1));
+ }
+ if (!key) value = regenerate(value, value, {});
+ // again, only one needed, do not use the RegExp for this replacement
+ // only keys need the RegExp
+ if (isString) value = value.replace(safeSpecialChar, specialChar);
+ return reviver ? reviver(key, value) : value;
+ };
+}
+
+function regenerateArray(root, current, retrieve) {
+ for (var i = 0, length = current.length; i < length; i++) {
+ current[i] = regenerate(root, current[i], retrieve);
+ }
+ return current;
+}
+
+function regenerateObject(root, current, retrieve) {
+ for (var key in current) {
+ if (current.hasOwnProperty(key)) {
+ current[key] = regenerate(root, current[key], retrieve);
+ }
+ }
+ return current;
+}
+
+function regenerate(root, current, retrieve) {
+ return current instanceof Array ?
+ // fast Array reconstruction
+ regenerateArray(root, current, retrieve) :
+ (
+ current instanceof $String ?
+ (
+ // root is an empty string
+ current.length ?
+ (
+ retrieve.hasOwnProperty(current) ?
+ retrieve[current] :
+ retrieve[current] = retrieveFromPath(
+ root, current.split(specialChar)
+ )
+ ) :
+ root
+ ) :
+ (
+ current instanceof Object ?
+ // dedicated Object parser
+ regenerateObject(root, current, retrieve) :
+ // value as it is
+ current
+ )
+ )
+ ;
+}
+
+function stringifyRecursion(value, replacer, space) {
+ return JSON.stringify(value, generateReplacer(value, replacer), space);
+}
+
+function parseRecursion(text, reviver) {
+ return JSON.parse(text, generateReviver(reviver));
+}
+return {
+ stringify: stringifyRecursion,
+ parse: parseRecursion
+};
+}(JSON, RegExp)));
View
140 build/circular-json.max.js
@@ -20,4 +20,142 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
-var main = {};
+var CircularJSON = (function(JSON, RegExp){
+var
+ // should be a not so common char
+ // possibly one JSON does not encode
+ // possibly one encodeURIComponent does not encode
+ // right now this char is '~' but this might change in the future
+ specialChar = '~',
+ safeSpecialChar = '\\x' + (
+ '0' + specialChar.charCodeAt(0).toString(16)
+ ).slice(-2),
+ specialCharRG = new RegExp(safeSpecialChar, 'g'),
+ safeSpecialCharRG = new RegExp('\\' + safeSpecialChar, 'g'),
+ indexOf = [].indexOf || function(v){
+ for(var i=this.length;i--&&this[i]!==v;);
+ return i;
+ },
+ $String = String // there's no way to drop warnings in JSHint
+ // about new String ... well, I need that here!
+ // faked, and happy linter!
+;
+
+function generateReplacer(value, replacer) {
+ var
+ path = [],
+ seen = [value],
+ mapp = [specialChar],
+ i
+ ;
+ return function(key, value) {
+ // the replacer has rights to decide
+ // if a new object should be returned
+ // or if there's some key to drop
+ // let's call it here rather than "too late"
+ if (replacer) value = replacer(key, value);
+
+ // did you know ? Safari passes keys as integers for arrays
+ if (key !== '') {
+ if (typeof value === 'object' && value) {
+ i = indexOf.call(seen, value);
+ if (i < 0) {
+ // key cannot contain specialChar but could be not a string
+ path.push(('' + key).replace(specialCharRG, safeSpecialChar));
+ mapp[seen.push(value) - 1] = specialChar + path.join(specialChar);
+ } else {
+ value = mapp[i];
+ }
+ } else {
+ path.pop();
+ if (typeof value === 'string') {
+ // ensure no special char involved on deserialization
+ // in this case only first char is important
+ // no need to replace all value (better performance)
+ value = value.replace(specialChar, safeSpecialChar);
+ }
+ }
+ }
+ return value;
+ };
+}
+
+function retrieveFromPath(current, keys) {
+ for(var i = 0, length = keys.length; i < length; current = current[
+ // keys should be normalized back here
+ keys[i++].replace(safeSpecialCharRG, specialChar)
+ ]);
+ return current;
+}
+
+function generateReviver(reviver) {
+ return function(key, value) {
+ var isString = typeof value === 'string';
+ if (isString && value.charAt(0) === specialChar) {
+ return new $String(value.slice(1));
+ }
+ if (!key) value = regenerate(value, value, {});
+ // again, only one needed, do not use the RegExp for this replacement
+ // only keys need the RegExp
+ if (isString) value = value.replace(safeSpecialChar, specialChar);
+ return reviver ? reviver(key, value) : value;
+ };
+}
+
+function regenerateArray(root, current, retrieve) {
+ for (var i = 0, length = current.length; i < length; i++) {
+ current[i] = regenerate(root, current[i], retrieve);
+ }
+ return current;
+}
+
+function regenerateObject(root, current, retrieve) {
+ for (var key in current) {
+ if (current.hasOwnProperty(key)) {
+ current[key] = regenerate(root, current[key], retrieve);
+ }
+ }
+ return current;
+}
+
+function regenerate(root, current, retrieve) {
+ return current instanceof Array ?
+ // fast Array reconstruction
+ regenerateArray(root, current, retrieve) :
+ (
+ current instanceof $String ?
+ (
+ // root is an empty string
+ current.length ?
+ (
+ retrieve.hasOwnProperty(current) ?
+ retrieve[current] :
+ retrieve[current] = retrieveFromPath(
+ root, current.split(specialChar)
+ )
+ ) :
+ root
+ ) :
+ (
+ current instanceof Object ?
+ // dedicated Object parser
+ regenerateObject(root, current, retrieve) :
+ // value as it is
+ current
+ )
+ )
+ ;
+}
+
+function stringifyRecursion(value, replacer, space) {
+ return JSON.stringify(value, generateReplacer(value, replacer), space);
+}
+
+function parseRecursion(text, reviver) {
+ return JSON.parse(text, generateReviver(reviver));
+}
+return {
+ stringify: stringifyRecursion,
+ parse: parseRecursion
+};
+}(JSON, RegExp));
View
136 build/circular-json.node.js
@@ -20,4 +20,138 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
-module.exports = {};
+var
+ // should be a not so common char
+ // possibly one JSON does not encode
+ // possibly one encodeURIComponent does not encode
+ // right now this char is '~' but this might change in the future
+ specialChar = '~',
+ safeSpecialChar = '\\x' + (
+ '0' + specialChar.charCodeAt(0).toString(16)
+ ).slice(-2),
+ specialCharRG = new RegExp(safeSpecialChar, 'g'),
+ safeSpecialCharRG = new RegExp('\\' + safeSpecialChar, 'g'),
+ indexOf = [].indexOf || function(v){
+ for(var i=this.length;i--&&this[i]!==v;);
+ return i;
+ },
+ $String = String // there's no way to drop warnings in JSHint
+ // about new String ... well, I need that here!
+ // faked, and happy linter!
+;
+
+function generateReplacer(value, replacer) {
+ var
+ path = [],
+ seen = [value],
+ mapp = [specialChar],
+ i
+ ;
+ return function(key, value) {
+ // the replacer has rights to decide
+ // if a new object should be returned
+ // or if there's some key to drop
+ // let's call it here rather than "too late"
+ if (replacer) value = replacer(key, value);
+
+ // did you know ? Safari passes keys as integers for arrays
+ if (key !== '') {
+ if (typeof value === 'object' && value) {
+ i = indexOf.call(seen, value);
+ if (i < 0) {
+ // key cannot contain specialChar but could be not a string
+ path.push(('' + key).replace(specialCharRG, safeSpecialChar));
+ mapp[seen.push(value) - 1] = specialChar + path.join(specialChar);
+ } else {
+ value = mapp[i];
+ }
+ } else {
+ path.pop();
+ if (typeof value === 'string') {
+ // ensure no special char involved on deserialization
+ // in this case only first char is important
+ // no need to replace all value (better performance)
+ value = value.replace(specialChar, safeSpecialChar);
+ }
+ }
+ }
+ return value;
+ };
+}
+
+function retrieveFromPath(current, keys) {
+ for(var i = 0, length = keys.length; i < length; current = current[
+ // keys should be normalized back here
+ keys[i++].replace(safeSpecialCharRG, specialChar)
+ ]);
+ return current;
+}
+
+function generateReviver(reviver) {
+ return function(key, value) {
+ var isString = typeof value === 'string';
+ if (isString && value.charAt(0) === specialChar) {
+ return new $String(value.slice(1));
+ }
+ if (!key) value = regenerate(value, value, {});
+ // again, only one needed, do not use the RegExp for this replacement
+ // only keys need the RegExp
+ if (isString) value = value.replace(safeSpecialChar, specialChar);
+ return reviver ? reviver(key, value) : value;
+ };
+}
+
+function regenerateArray(root, current, retrieve) {
+ for (var i = 0, length = current.length; i < length; i++) {
+ current[i] = regenerate(root, current[i], retrieve);
+ }
+ return current;
+}
+
+function regenerateObject(root, current, retrieve) {
+ for (var key in current) {
+ if (current.hasOwnProperty(key)) {
+ current[key] = regenerate(root, current[key], retrieve);
+ }
+ }
+ return current;
+}
+
+function regenerate(root, current, retrieve) {
+ return current instanceof Array ?
+ // fast Array reconstruction
+ regenerateArray(root, current, retrieve) :
+ (
+ current instanceof $String ?
+ (
+ // root is an empty string
+ current.length ?
+ (
+ retrieve.hasOwnProperty(current) ?
+ retrieve[current] :
+ retrieve[current] = retrieveFromPath(
+ root, current.split(specialChar)
+ )
+ ) :
+ root
+ ) :
+ (
+ current instanceof Object ?
+ // dedicated Object parser
+ regenerateObject(root, current, retrieve) :
+ // value as it is
+ current
+ )
+ )
+ ;
+}
+
+function stringifyRecursion(value, replacer, space) {
+ return JSON.stringify(value, generateReplacer(value, replacer), space);
+}
+
+function parseRecursion(text, reviver) {
+ return JSON.parse(text, generateReviver(reviver));
+}
+this.stringify = stringifyRecursion;
+this.parse = parseRecursion;
View
4 index.html
@@ -4,10 +4,10 @@
<title>wru test</title>
<script>
// here the list of tests to run with wru
- var TESTS = ["main"];
+ var TESTS = ["circular-json"];
</script>
<script>// don't change code here
- for(var i = 0; i < TESTS.length; ++i && document.write('<script src="src/' + TESTS + '.js"><' + '/script>'));
+ for(var i = 0; i < TESTS.length; ++i && document.write('<script src="build/' + TESTS + '.max.js"><' + '/script>'));
function wru(wru){
var
all = [],
View
11 package.json
@@ -1,12 +1,13 @@
{
- "version": "0.0.0",
+ "version": "0.1.0",
"name": "circular-json",
- "description": "",
+ "description": "JSON does not handle circular references. This version does",
"homepage": "https://github.com/WebReflection/circular-json",
- "keywords": [],
+ "keywords": ["JSON", "circular", "reference", "recursive", "recursion", "parse", "stringify"],
+ "generator": "https://github.com/WebReflection/gitstrap",
"author": {
- "name": "",
- "web": ""
+ "name": "Andrea Giammarchi",
+ "web": "http://webreflection.blogspot.com/"
},
"repository": {
"type": "git",
View
134 src/circular-json.js
@@ -1 +1,133 @@
-{}
+var
+ // should be a not so common char
+ // possibly one JSON does not encode
+ // possibly one encodeURIComponent does not encode
+ // right now this char is '~' but this might change in the future
+ specialChar = '~',
+ safeSpecialChar = '\\x' + (
+ '0' + specialChar.charCodeAt(0).toString(16)
+ ).slice(-2),
+ specialCharRG = new RegExp(safeSpecialChar, 'g'),
+ safeSpecialCharRG = new RegExp('\\' + safeSpecialChar, 'g'),
+ indexOf = [].indexOf || function(v){
+ for(var i=this.length;i--&&this[i]!==v;);
+ return i;
+ },
+ $String = String // there's no way to drop warnings in JSHint
+ // about new String ... well, I need that here!
+ // faked, and happy linter!
+;
+
+function generateReplacer(value, replacer) {
+ var
+ path = [],
+ seen = [value],
+ mapp = [specialChar],
+ i
+ ;
+ return function(key, value) {
+ // the replacer has rights to decide
+ // if a new object should be returned
+ // or if there's some key to drop
+ // let's call it here rather than "too late"
+ if (replacer) value = replacer(key, value);
+
+ // did you know ? Safari passes keys as integers for arrays
+ if (key !== '') {
+ if (typeof value === 'object' && value) {
+ i = indexOf.call(seen, value);
+ if (i < 0) {
+ // key cannot contain specialChar but could be not a string
+ path.push(('' + key).replace(specialCharRG, safeSpecialChar));
+ mapp[seen.push(value) - 1] = specialChar + path.join(specialChar);
+ } else {
+ value = mapp[i];
+ }
+ } else {
+ path.pop();
+ if (typeof value === 'string') {
+ // ensure no special char involved on deserialization
+ // in this case only first char is important
+ // no need to replace all value (better performance)
+ value = value.replace(specialChar, safeSpecialChar);
+ }
+ }
+ }
+ return value;
+ };
+}
+
+function retrieveFromPath(current, keys) {
+ for(var i = 0, length = keys.length; i < length; current = current[
+ // keys should be normalized back here
+ keys[i++].replace(safeSpecialCharRG, specialChar)
+ ]);
+ return current;
+}
+
+function generateReviver(reviver) {
+ return function(key, value) {
+ var isString = typeof value === 'string';
+ if (isString && value.charAt(0) === specialChar) {
+ return new $String(value.slice(1));
+ }
+ if (!key) value = regenerate(value, value, {});
+ // again, only one needed, do not use the RegExp for this replacement
+ // only keys need the RegExp
+ if (isString) value = value.replace(safeSpecialChar, specialChar);
+ return reviver ? reviver(key, value) : value;
+ };
+}
+
+function regenerateArray(root, current, retrieve) {
+ for (var i = 0, length = current.length; i < length; i++) {
+ current[i] = regenerate(root, current[i], retrieve);
+ }
+ return current;
+}
+
+function regenerateObject(root, current, retrieve) {
+ for (var key in current) {
+ if (current.hasOwnProperty(key)) {
+ current[key] = regenerate(root, current[key], retrieve);
+ }
+ }
+ return current;
+}
+
+function regenerate(root, current, retrieve) {
+ return current instanceof Array ?
+ // fast Array reconstruction
+ regenerateArray(root, current, retrieve) :
+ (
+ current instanceof $String ?
+ (
+ // root is an empty string
+ current.length ?
+ (
+ retrieve.hasOwnProperty(current) ?
+ retrieve[current] :
+ retrieve[current] = retrieveFromPath(
+ root, current.split(specialChar)
+ )
+ ) :
+ root
+ ) :
+ (
+ current instanceof Object ?
+ // dedicated Object parser
+ regenerateObject(root, current, retrieve) :
+ // value as it is
+ current
+ )
+ )
+ ;
+}
+
+function stringifyRecursion(value, replacer, space) {
+ return JSON.stringify(value, generateReplacer(value, replacer), space);
+}
+
+function parseRecursion(text, reviver) {
+ return JSON.parse(text, generateReviver(reviver));
+}
View
7 template/amd.after
@@ -1 +1,6 @@
-);
+
+return {
+ stringify: stringifyRecursion,
+ parse: parseRecursion
+};
+}(JSON, RegExp)));
View
2  template/amd.before
@@ -1 +1 @@
-define(
+define((function(JSON, RegExp){
View
4 template/node.after
@@ -1 +1,3 @@
-;
+
+this.stringify = stringifyRecursion;
+this.parse = parseRecursion;
View
1  template/node.before
@@ -1 +0,0 @@
-module.exports =
View
7 template/var.after
@@ -1 +1,6 @@
-;
+
+return {
+ stringify: stringifyRecursion,
+ parse: parseRecursion
+};
+}(JSON, RegExp));
View
2  template/var.before
@@ -1 +1 @@
-var main =
+var CircularJSON = (function(JSON, RegExp){
View
97 test/circular-json.js
@@ -1,13 +1,102 @@
//remove:
-var main = require('../build/circular-json.node.js');
+var CircularJSON = require('../build/circular-json.node.js');
//:remove
wru.test([
{
- name: "main",
+ name: "CircularJSON",
test: function () {
- wru.assert(typeof main == "object");
- // wru.assert(0);
+ var o = {a: 'a', b: 'b', c: function(){}, d: {e: 123}},
+ a, b;
+ wru.assert('loaded', typeof CircularJSON == "object");
+ wru.assert('works as JSON.stringify',
+ (a = JSON.stringify(o)) === (b = CircularJSON.stringify(o)));
+ wru.assert('works as JSON.parse',
+ JSON.stringify(JSON.parse(a)) === JSON.stringify(CircularJSON.parse(b)));
+ wru.assert('accept callback', CircularJSON.stringify(o, function(key, value){
+ if (!key || key === 'a') return value;
+ }) === '{"a":"a"}');
+ wru.assert('revive callback', JSON.stringify(
+ CircularJSON.parse('{"a":"a"}', function(key, value){
+ if (key === 'a') return 'b';
+ return value;
+ })
+ ) === '{"a":"b"}');
+ }
+ },{
+ name: 'recursion',
+ test: function () {
+ var o = {}, before, after;
+ o.a = o;
+ o.c = {};
+ o.d = {
+ a: 123,
+ b: o
+ };
+ o.c.e = o;
+ o.c.f = o.d;
+ o.b = o.c;
+ before = CircularJSON.stringify(o);
+ o = CircularJSON.parse(before);
+ wru.assert('recreated original structure',
+ o.b === o.c &&
+ o.c.e === o &&
+ o.d.a === 123 &&
+ o.d.b === o &&
+ o.c.f === o.d &&
+ o.b === o.c
+ );
+ }
+ },{
+ name: 'recursion And Functions',
+ test: function () {
+ var o = {};
+ o.a = o;
+ o.b = o;
+ wru.assert('callback invoked',
+ CircularJSON.stringify(o, function (key, value) {
+ if (!key || key === 'a') return value;
+ }) === '{"a":"~"}'
+ );
+ o = CircularJSON.parse('{"a":"~"}', function (key, value) {
+ if (!key) {
+ value.b = value;
+ }
+ return value;
+ });
+ wru.assert('reviver invoked',
+ o.a === o && o.b === o
+ );
+ }
+ },{
+ name: 'try to screw logic',
+ test: function () {
+ var o = {};
+ o['~'] = o;
+ o.test = '~';
+ o = CircularJSON.parse(CircularJSON.stringify(o));
+ wru.assert('still intact', o['~'] === o && o.test === '~');
+ o = {
+ a: [
+ '~', '~~', '~~~'
+ ]
+ };
+ o.a.push(o);
+ o.o = o;
+ o['~'] = o.a;
+ o['~~'] = o.a;
+ o['~~~'] = o.a;
+ o = CircularJSON.parse(CircularJSON.stringify(o));
+ wru.assert('restructured',
+ o === o.a[3] &&
+ o === o.o &&
+ o['~'] === o.a &&
+ o['~~'] === o.a &&
+ o['~~~'] === o.a &&
+ o.a === o.a[3].a &&
+ o.a.pop() === o &&
+ o.a.join('') === '~~~~~~'
+ );
}
}
]);
Please sign in to comment.
Something went wrong with that request. Please try again.