|
| 1 | +/* |
| 2 | + cycle.js |
| 3 | + 2017-02-07 |
| 4 | +
|
| 5 | + Public Domain. |
| 6 | +
|
| 7 | + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. |
| 8 | +
|
| 9 | + This code should be minified before deployment. |
| 10 | + See http://javascript.crockford.com/jsmin.html |
| 11 | +
|
| 12 | + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO |
| 13 | + NOT CONTROL. |
| 14 | +*/ |
| 15 | + |
| 16 | +// The file uses the WeakMap feature of ES6. |
| 17 | + |
| 18 | +/*jslint es6, eval */ |
| 19 | + |
| 20 | +/*property |
| 21 | + $ref, decycle, forEach, get, indexOf, isArray, keys, length, push, |
| 22 | + retrocycle, set, stringify, test |
| 23 | +*/ |
| 24 | + |
| 25 | +if (typeof JSON.decycle !== "function") { |
| 26 | + JSON.decycle = function decycle(object, replacer) { |
| 27 | + "use strict"; |
| 28 | + |
| 29 | +// Make a deep copy of an object or array, assuring that there is at most |
| 30 | +// one instance of each object or array in the resulting structure. The |
| 31 | +// duplicate references (which might be forming cycles) are replaced with |
| 32 | +// an object of the form |
| 33 | + |
| 34 | +// {"$ref": PATH} |
| 35 | + |
| 36 | +// where the PATH is a JSONPath string that locates the first occurance. |
| 37 | + |
| 38 | +// So, |
| 39 | + |
| 40 | +// var a = []; |
| 41 | +// a[0] = a; |
| 42 | +// return JSON.stringify(JSON.decycle(a)); |
| 43 | + |
| 44 | +// produces the string '[{"$ref":"$"}]'. |
| 45 | + |
| 46 | +// If a replacer function is provided, then it will be called for each value. |
| 47 | +// A replacer function receives a value and returns a replacement value. |
| 48 | + |
| 49 | +// JSONPath is used to locate the unique object. $ indicates the top level of |
| 50 | +// the object or array. [NUMBER] or [STRING] indicates a child element or |
| 51 | +// property. |
| 52 | + |
| 53 | + var objects = new WeakMap(); // object to path mappings |
| 54 | + |
| 55 | + return (function derez(value, path) { |
| 56 | + |
| 57 | +// The derez function recurses through the object, producing the deep copy. |
| 58 | + |
| 59 | + var old_path; // The path of an earlier occurance of value |
| 60 | + var nu; // The new object or array |
| 61 | + |
| 62 | +// If a replacer function was provided, then call it to get a replacement value. |
| 63 | + |
| 64 | + if (replacer !== undefined) { |
| 65 | + value = replacer(value); |
| 66 | + } |
| 67 | + |
| 68 | +// typeof null === "object", so go on if this value is really an object but not |
| 69 | +// one of the weird builtin objects. |
| 70 | + |
| 71 | + if ( |
| 72 | + typeof value === "object" && value !== null && |
| 73 | + !(value instanceof Boolean) && |
| 74 | + !(value instanceof Date) && |
| 75 | + !(value instanceof Number) && |
| 76 | + !(value instanceof RegExp) && |
| 77 | + !(value instanceof String) |
| 78 | + ) { |
| 79 | + |
| 80 | +// If the value is an object or array, look to see if we have already |
| 81 | +// encountered it. If so, return a {"$ref":PATH} object. This uses an |
| 82 | +// ES6 WeakMap. |
| 83 | + |
| 84 | + old_path = objects.get(value); |
| 85 | + if (old_path !== undefined) { |
| 86 | + return {$ref: old_path}; |
| 87 | + } |
| 88 | + |
| 89 | +// Otherwise, accumulate the unique value and its path. |
| 90 | + |
| 91 | + objects.set(value, path); |
| 92 | + |
| 93 | +// If it is an array, replicate the array. |
| 94 | + |
| 95 | + if (Array.isArray(value)) { |
| 96 | + nu = []; |
| 97 | + value.forEach(function (element, i) { |
| 98 | + nu[i] = derez(element, path + "[" + i + "]"); |
| 99 | + }); |
| 100 | + } else { |
| 101 | + |
| 102 | +// If it is an object, replicate the object. |
| 103 | + |
| 104 | + nu = {}; |
| 105 | + Object.keys(value).forEach(function (name) { |
| 106 | + nu[name] = derez( |
| 107 | + value[name], |
| 108 | + path + "[" + JSON.stringify(name) + "]" |
| 109 | + ); |
| 110 | + }); |
| 111 | + } |
| 112 | + return nu; |
| 113 | + } |
| 114 | + return value; |
| 115 | + }(object, "$")); |
| 116 | + }; |
| 117 | +} |
| 118 | + |
| 119 | + |
| 120 | +if (typeof JSON.retrocycle !== "function") { |
| 121 | + JSON.retrocycle = function retrocycle($) { |
| 122 | + "use strict"; |
| 123 | + |
| 124 | +// Restore an object that was reduced by decycle. Members whose values are |
| 125 | +// objects of the form |
| 126 | +// {$ref: PATH} |
| 127 | +// are replaced with references to the value found by the PATH. This will |
| 128 | +// restore cycles. The object will be mutated. |
| 129 | + |
| 130 | +// The eval function is used to locate the values described by a PATH. The |
| 131 | +// root object is kept in a $ variable. A regular expression is used to |
| 132 | +// assure that the PATH is extremely well formed. The regexp contains nested |
| 133 | +// * quantifiers. That has been known to have extremely bad performance |
| 134 | +// problems on some browsers for very long strings. A PATH is expected to be |
| 135 | +// reasonably short. A PATH is allowed to belong to a very restricted subset of |
| 136 | +// Goessner's JSONPath. |
| 137 | + |
| 138 | +// So, |
| 139 | +// var s = '[{"$ref":"$"}]'; |
| 140 | +// return JSON.retrocycle(JSON.parse(s)); |
| 141 | +// produces an array containing a single element which is the array itself. |
| 142 | + |
| 143 | + var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\([\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/; |
| 144 | + |
| 145 | + (function rez(value) { |
| 146 | + |
| 147 | +// The rez function walks recursively through the object looking for $ref |
| 148 | +// properties. When it finds one that has a value that is a path, then it |
| 149 | +// replaces the $ref object with a reference to the value that is found by |
| 150 | +// the path. |
| 151 | + |
| 152 | + if (value && typeof value === "object") { |
| 153 | + if (Array.isArray(value)) { |
| 154 | + value.forEach(function (element, i) { |
| 155 | + if (typeof element === "object" && element !== null) { |
| 156 | + var path = element.$ref; |
| 157 | + if (typeof path === "string" && px.test(path)) { |
| 158 | + value[i] = eval(path); |
| 159 | + } else { |
| 160 | + rez(element); |
| 161 | + } |
| 162 | + } |
| 163 | + }); |
| 164 | + } else { |
| 165 | + Object.keys(value).forEach(function (name) { |
| 166 | + var item = value[name]; |
| 167 | + if (typeof item === "object" && item !== null) { |
| 168 | + var path = item.$ref; |
| 169 | + if (typeof path === "string" && px.test(path)) { |
| 170 | + value[name] = eval(path); |
| 171 | + } else { |
| 172 | + rez(item); |
| 173 | + } |
| 174 | + } |
| 175 | + }); |
| 176 | + } |
| 177 | + } |
| 178 | + }($)); |
| 179 | + return $; |
| 180 | + }; |
| 181 | +} |
0 commit comments