Permalink
Browse files

first commit with tests

  • Loading branch information...
1 parent 8c3b2e1 commit c9c3e3a459e5145b82ed003ef3cde6595ebe59da Andrea Giammarchi committed Mar 13, 2013
View
@@ -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
@@ -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.

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -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)));
Oops, something went wrong.

0 comments on commit c9c3e3a

Please sign in to comment.