Skip to content
This repository has been archived by the owner on Oct 23, 2019. It is now read-only.

Commit

Permalink
first commit with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrea Giammarchi committed Mar 13, 2013
1 parent 8c3b2e1 commit c9c3e3a
Show file tree
Hide file tree
Showing 17 changed files with 752 additions and 26 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ NODE = $(VAR)
# make amd files # make amd files
AMD = $(VAR) AMD = $(VAR)



# README constant # README constant




Expand Down Expand Up @@ -101,6 +102,6 @@ dependencies:
mkdir node_modules mkdir node_modules
npm install wru npm install wru
npm install polpetta npm install polpetta
npm install markdown
npm install uglify-js@1 npm install uglify-js@1
npm install jshint npm install jshint
npm install markdown
86 changes: 84 additions & 2 deletions README.md
Original file line number Original file line Diff line number Diff line change
@@ -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) [![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.
2 changes: 1 addition & 1 deletion build/circular-json.amd.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/circular-json.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

140 changes: 139 additions & 1 deletion build/circular-json.max.amd.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -20,4 +20,142 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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)));
Loading

0 comments on commit c9c3e3a

Please sign in to comment.