123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- /*!
- Copyright (C) 2013-2017 by Andrea Giammarchi - @WebReflection
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
- 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),
- 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 // 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, 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) {
- // 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 (inspect) value = fn.call(this, key, value);
- // first pass should be ignored, since it's just the initial object
- 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;
- }
- // console.log(lvl, key, path);
- if (typeof value === 'object' && value) {
- // if object isn't referring to parent object, add to the
- // object path stack. Otherwise it is already there.
- 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) {
- // key cannot contain specialChar but could be not a string
- 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) {
- // 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(safeSpecialChar, escapedSafeSpecialChar)
- .replace(specialChar, safeSpecialChar);
- }
- }
- } else {
- doNotIgnore = true;
- }
- 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(safeStartWithSpecialCharRG, '$1' + specialChar)
- .replace(escapedSafeSpecialChar, safeSpecialChar);
- return reviver ? reviver.call(this, 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
- )
- )
- ;
- }
- var CircularJSON = {
- stringify: function stringify(value, replacer, space, doNotResolve) {
- return CircularJSON.parser.stringify(
- value,
- generateReplacer(value, replacer, !doNotResolve),
- space
- );
- },
- parse: function parse(text, reviver) {
- return CircularJSON.parser.parse(
- text,
- generateReviver(reviver)
- );
- },
- // A parser should be an API 1:1 compatible with JSON
- // it should expose stringify and parse methods.
- // The default parser is the native JSON.
- parser: JSON
- };
- return CircularJSON;
- }(JSON, RegExp));
|