deep-clone-and-serialize
Version:
Deep clone and/or serialize almost any JavaScript object tree (Map/Set, RegExp, DataView, etc.) while preserving circular references.
884 lines (782 loc) • 27.8 kB
JavaScript
/*
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.
use context in this project: Serializing functions
*/
;
// Generate an internal UID to make the regexp pattern harder to guess.
var UID = Math.floor(Math.random() * 0x10000000000).toString(16);
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R|D)-' + UID + '-(\\d+)__@"', 'g');
var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g;
// Mapping of unsafe HTML and invalid JavaScript line terminator chars to their
// Unicode char counterparts which are safe to use in JavaScript strings.
var ESCAPED_CHARS = {
'<': '\\u003C',
'>': '\\u003E',
'/': '\\u002F',
'\u2028': '\\u2028',
'\u2029': '\\u2029'
};
function escapeUnsafeChars(unsafeChar) {
return ESCAPED_CHARS[unsafeChar];
}
//module.exports = function serialize(obj, options) {
var serialize = function (obj, options) {
options || (options = {});
// Backwards-compatibility for `space` as the second argument.
if (typeof options === 'number' || typeof options === 'string') {
options = { space: options };
}
var functions = [];
var regexps = [];
var dates = [];
// Returns placeholders for functions and regexps (identified by index)
// which are later replaced by their string representation.
function replacer(key, value) {
if (!value) {
return value;
}
// If the value is an object w/ a toJSON method, toJSON is called before
// the replacer runs, so we use this[key] to get the non-toJSONed value.
var origValue = this[key];
var type = typeof origValue;
if (type === 'object') {
if (origValue instanceof RegExp) {
return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';
}
if (origValue instanceof Date) {
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
}
}
if (type === 'function') {
return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@';
}
return value;
}
var str;
// Creates a JSON string representation of the value.
// NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args.
if (options.isJSON && !options.space) {
str = JSON.stringify(obj);
} else {
str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space);
}
// Protects against `JSON.stringify()` returning `undefined`, by serializing
// to the literal string: "undefined".
if (typeof str !== 'string') {
return String(str);
}
// Replace unsafe HTML and invalid JavaScript line terminator chars with
// their safe Unicode char counterpart. This _must_ happen before the
// regexps and functions are serialized and added back to the string.
if (options.unsafe !== true) {
str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);
}
if (functions.length === 0 && regexps.length === 0 && dates.length === 0) {
return str;
}
// Replaces all occurrences of function, regexp and date placeholders in the
// JSON string with their string representations. If the original value can
// not be found, then `undefined` is used.
return str.replace(PLACE_HOLDER_REGEXP, function (match, type, valueIndex) {
if (type === 'D') {
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
}
if (type === 'R') {
return regexps[valueIndex].toString();
}
var fn = functions[valueIndex];
var serializedFn = fn.toString();
if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {
throw new TypeError('Serializing native function: ' + fn.name);
}
return serializedFn;
});
}
/**
* ES6 Map like object. Author:Azu
* https://github.com/azu/map-like/blob/master/src/MapLike.js
* See [Map - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map - JavaScript | MDN")
* Here, Only used as a fall back for packer. Does not polyfill Map functionality
*/
// LICENSE : MIT
;
// constants
const NanSymbolMark = {};
function encodeKey(key) {
const isNotNumber = typeof key === "number" && key !== key;
return isNotNumber ? NanSymbolMark : key;
}
function decodeKey(encodedKey) {
return (encodedKey === NanSymbolMark) ? NaN : encodedKey;
}
class MapLike {
constructor(entries = []) {
this._keys = [];
this._values = [];
entries.forEach(entry => {
if (!Array.isArray(entry)) {
throw new Error("should be `new MapLike([ [key, value] ])`");
}
if (entry.length !== 2) {
throw new Error("should be `new MapLike([ [key, value] ])`");
}
this.set(entry[0], entry[1]);
});
}
get size() {
return this._values.filter(value => value !== undefined).length;
}
entries() {
return this.keys().map(key => {
var value = this.get(key);
return [decodeKey(key), value];
});
}
keys() {
return this._keys.filter(value => value !== undefined).map(decodeKey);
}
values() {
return this._values.slice();
}
get(key) {
const idx = this._keys.indexOf(encodeKey(key));
return (idx !== -1) ? this._values[idx] : undefined;
}
has(key) {
return (this._keys.indexOf(encodeKey(key)) !== -1);
}
set(key, value) {
const idx = this._keys.indexOf(encodeKey(key));
if (idx !== -1) {
this._values[idx] = value;
} else {
this._keys.push(encodeKey(key));
this._values.push(value);
}
return this;
}
delete(key) {
const idx = this._keys.indexOf(encodeKey(key));
if (idx === -1) {
return false;
}
this._keys.splice(idx, 1);
this._values.splice(idx, 1);
return true;
}
clear() {
this._keys = [];
this._values = [];
return this;
}
forEach(handler, thisArg) {
this.keys().forEach(key => {
// value, key, map
handler(this.get(key), key, thisArg || this);
});
}
}
var pMap = Map || MapLike
/*
copyright 2018 Conley Johnson
license MIT
/*
objects are laid out in a registry (objects only.)
object[pieces] refers to objects, not primitives
calling custom constructors...
arguments can be missing
composite construction could render an entirely different object with different arguments or context
arguments could be out of date, even if you had access to them
the operation of an object could have had critical impact on private variables, and this would render the clone inoperable
*/
var arrayWrap = function (thing) {
if (Object.prototype.toString.call(thing) !== '[object Array]') {
return [thing]
} else { return thing }
}
var waterMarks = {
__$_VAL: 1,
__$_TYPE: 1,
__$_LEN: 1,
__$_ORIGLEN: 1,
__$_buffer: 1,
__$_byteOffset: 1,
_HS: 1,
_M: 1,
}
function cleanClone(p = {}) {
var { clone, leaveHSandM = false } = p
for (let k in waterMarks) {
if (leaveHSandM && (k === '_HS' || k === '_M')) { continue; }
if (clone !== undefined && clone.hasOwnProperty(k)) { delete clone[k] }
}
}
var getOwn = function (to, from) {
for (let a in from) {
try { to[a] = from[a] } catch (e) { }
}
}
export function Dupify() {
this.filters = new pMap()
this.makeMissiles = function (p = {}) {
//objects are normalized now in objPieces, and reverse noremalized in the registry. crosslink missiles
var { registry, objPieces } = p
for (var a in objPieces) {
var piece = objPieces[a]
for (var k in piece) {
if (registry.has(piece[k])) {
piece[k] = { _M: registry.get(piece[k]) }//registry returns the counter ID for that object
}
}
}
}
this.clone = function (p = {}) {
return this.deepClone(p.obj, p)
}
this.pack = function (p = {}) {//takes obj, stopProp
p.JSONReady = true
var normal = this.deepClone(p.obj, p)
var { registry, objPieces } = normal
this.makeMissiles(normal)
return JSON.stringify(objPieces)
}
///////////////unpack
this.removeHeatSources = function (p = {}) {
var { objPieces } = p
for (var k in objPieces) { if (objPieces[k]._HS !== undefined) { delete objPieces[k]._HS } }
}
this.cleanPieces = function (p = {}) {
var { objPieces } = p
for (var k in objPieces) {
cleanClone({ clone: objPieces[k] })
}
}
this.layoutPieces = function (p = {}) {
var { obj } = p
var objPieces = obj
if (typeof obj === 'string') {
objPieces = JSON.parse(obj)//objpieces props should correspond to heatsources
}
var registry = new pMap()
for (var k in objPieces) { registry.set(objPieces[k], objPieces[k]._HS) }
return { registry, objPieces }
}
this.recastObjectTypes = function (p = {}) {
var { objPieces } = p
//preloop to reconstruct buffers first. they must be constructed at the time of the recreation of their views
for (let a in objPieces) {
var piece = objPieces[a]//storage
if (piece.__$_TYPE !== 'ArrayBuffer') { continue; }
var recast = determineConversionSpace(piece.__$_TYPE)
objPieces[a] = recast.in({ obj: piece, objPieces })
getOwn(objPieces[a], piece)//reassign the enumerables if any
objPieces[a].__$_TYPE = 'ArrayBuffer'//may have to switch to instance of checks in the future.
}
for (let a in objPieces) {
var piece = objPieces[a]//storage
if (piece.__$_TYPE === 'ArrayBuffer') { continue; }
var recast = determineConversionSpace(piece.__$_TYPE)
objPieces[a] = recast.in({ obj: piece, objPieces })
getOwn(objPieces[a], piece)//reassign the enumerables if any
}
}
var kernels = new pMap()
kernels.set('__$_NaN', 'df' / 4)
kernels.set('__$_undefined', undefined)
kernels.set('__$_Infinity', Infinity)
kernels.set('__$_-Infinity', -Infinity)
this.fixKernels = function (piece, k) {
if (kernels.has(piece[k])) { piece[k] = kernels.get(piece[k]) }
}
this.putKernels = function (clone) {
if (Number.isNaN(clone)) { return '__$_NaN' }
if (clone === undefined) { return '__$_undefined' }
if (clone === Infinity) { return '__$_Infinity' }
if (clone === -Infinity) { return '__$_-Infinity' }
return clone
}
this.deployMissiles = function (p = {}) {
var { registry, objPieces } = p
for (var a in objPieces) {
var piece = objPieces[a]
if (piece.constructor && piece.constructor === Map) {
var additions = []
var tearDown = function (v, k) {
piece.delete(k);
additions.push([
kernels.has(k) ? kernels.get(k) : objPieces[k._M] || k,
kernels.has(v) ? kernels.get(v) : objPieces[v._M] || v
])
}
var build = function (val) {
piece.set(val[0], val[1])
}
piece.forEach(tearDown)
additions.forEach(build)
}
else if (piece.constructor && piece.constructor === Set) {
var additions = []
var tearDown = function (v) {
piece.delete(v);
additions.push(kernels.has(v) ? kernels.get(v) : objPieces[v._M] || v)
}
var build = function (val) {
piece.add(val)
}
piece.forEach(tearDown)
additions.forEach(build)
}
for (var k in piece) {
if (piece[k] && piece[k]._M !== undefined) {//checking a property on a null value kicks out an error
piece[k] = objPieces[piece[k]._M]//objPieces prop should corresont to heatsource
}
this.fixKernels(piece, k)
}
}
}
this.applyRevival = function (p = {}) {
var { objPieces, revival, suppressRevival } = p
var useRevival = suppressRevival ? false : (revival || this.revival)
if (useRevival) {
for (let a in objPieces) {
objPieces[a] = useRevival(objPieces[a])
}
}
}
this.unpack = function (p = {}) {
// layout in memory, and populate a registry
if (typeof p === 'string') { p = { obj: p } }
var normal = this.layoutPieces(p)
normal.revival = p.revival; normal.suppressRevival = p.suppressRevival
var { registry, objPieces } = normal
this.recastObjectTypes(normal)//still flattened
this.deployMissiles(normal)
this.cleanPieces(normal)
this.applyRevival(normal)
var headObj = objPieces[0]//get it before clearing the registry
registry.clear()
return headObj
}
}
Dupify.serialize = function (p) { return serialize(p) }
Dupify.prototype.serialize = Dupify.serialize
Dupify.prototype.transform = false
Dupify.prototype.setTransform = function (func) { this.transform = func; return this }
Dupify.prototype.unsetTransform = function () { this.transform = false; return this }
Dupify.prototype.revival = false
Dupify.prototype.setRevival = function (func) { this.revival = func; return this }
Dupify.prototype.unsetRevival = function () { this.revival = false; return this }
//Filter stuff
Dupify.prototype.addFilters = function (p = {}) {
p = arrayWrap(p)
var add = function (val) {
this.filters.set(val, 1)
}.bind(this)
p.forEach(add)
return this
}
Dupify.prototype.removeFilters = function (p = {}) {
p = arrayWrap(p)
var remove = function (val) {
if (this.filters.has(val)) {
this.filters.delete(val)
}
}.bind(this)
p.forEach(remove)//looping through the array, not the Map. so it's ok
return this
}
Dupify.prototype.clearFilters = function () {
this.filters.clear()
return this
}
Dupify.prototype.getFilters = function () {
var store = []
var add = function (v, k) { store.push(k) }
this.filters.forEach(add)
return store
}
var getPathParam = function (path, cont, key, value) {
return {
path,
next: {
containingObj: cont,
key,
value
}
}
}
var testFilters = function (path, cont, key, value, useFilters) {
if (!useFilters) { return true }
var param = getPathParam(path, cont, key, value)
return dFilter(param, useFilters)//filters returns boolean
}
var dFilter = function (p, filters = []) {
for (let i = 0, len = filters.length; i < len; i++) {
if (filters[i](p) === false) { return false }
}
return true
}
//end filter stuff
var isPrimitive = function (val) {
let type = typeof val
return (type === 'boolean' || type === 'string' || type === 'number' || type === 'symbol' || val === null || val === undefined)
}
Dupify.prototype.deepClone = function (obj, p = {}) {//preserves circular references. Function references point to the original
if (p.obj) { obj = p.obj }
// if (!obj || !((obj !== null && typeof obj === 'object') || typeof obj === 'function')) { return obj }//null,undefined,
if (isPrimitive(obj)) return obj
var {
stopProp = {},
registry = new pMap(),
cloneRegistry = new pMap(),
JSONReady = false,
objPieces = {},
transform = false,
filters = false,
duplicateFuncs = false,
suppressTransform = false,
suppressEnumerable = false,
suppressFilters = false,
observable = false
} = p
if (filters) { filters = arrayWrap(filters) }
var storedFilters = this.getFilters()
var useTransform = suppressTransform ? false : transform || this.transform
var useFilters = suppressFilters ? false : (filters || (storedFilters.length > 0 ? storedFilters : false))
var counter = 0, copy = 0
var path = []
var fork = function (obj, key, containingObj) {
var type = typeof obj, clone
if (obj !== null && type === 'object' || type === 'function') {
//have I seen you before?
if (registry.has(obj)) { clone = registry.get(obj) }
//otherwise, create new clone address, and store in registry in association with this object
else {
path.push({ key, value: obj, containingObj })//they make their decision before forking,
var useObj = useTransform ? useTransform(obj) : obj
//var useObj = checkObj === obj ? obj : checkObj
var cs = determineConversionSpace(useObj);
if (useObj === 'dupify->use_original_reference' && !JSONReady) clone = obj
else clone = JSONReady ?
{} : (observable ? cs.getProxy({ obj: useObj, registry, cloneRegistry, objPieces, duplicateFuncs }) : cs.cast({ obj: useObj, registry, cloneRegistry, objPieces, duplicateFuncs }))
registry.set(obj, clone)
if (JSONReady) {
clone._HS = counter
cloneRegistry.set(clone, counter);
objPieces[counter] = clone;
counter++
}
if (clone !== useObj && clone !== obj) {
propagate(useObj, clone)
}
path.pop()
}
}
else {//primitive If looking for a JSON, then the clone has to have the correct end ptoperties.
clone = useTransform ? useTransform(obj) : obj
if (JSONReady) {
clone = this.putKernels(clone)
}
}//primitive
return clone
}.bind(this)
var propagate = function (obj, clone) {//clone should be an empty object of the same type
var cs = determineConversionSpace(obj)
var enumSpace = conversion['Object']
var construct = cs.constructorType
var constring = constructStrings.get(construct)
cs[JSONReady ? 'outJSON' : 'out']({ obj, clone, fork, duplicateFuncs, path, useFilters })
if (!suppressEnumerable) { enumSpace[JSONReady ? 'outJSON' : 'out']({ obj, clone, fork, duplicateFuncs, path, useFilters }) }
}.bind(this)
var finalClone = fork(obj)
return JSONReady ? { registry: cloneRegistry, objPieces } : finalClone
}
var constructors = {
'Object': Object,
'Function': Function,
'Error': Error,
'EvalError': EvalError ? EvalError : Error,
'RangeError': RangeError ? RangeError : Error,
'ReferenceError': ReferenceError ? ReferenceError : Error,
'SyntaxError': SyntaxError ? SyntaxError : Error,
'TypeError': TypeError ? TypeError : Error,
'URIError': URIError ? URIError : Error,
'RegExp': RegExp ? RegExp : null,
'Array': Array,
'ArrayBuffer': ArrayBuffer ? ArrayBuffer : Array,
'Int8Array': Int8Array ? Int8Array : Array,
'Uint8Array': Uint8Array ? Uint8Array : Array,
'Uint8ClampedArray': Uint8ClampedArray ? Uint8ClampedArray : Array,
'Int16Array': Int16Array ? Int16Array : Array,
'Uint16Array': Uint16Array ? Uint16Array : Array,
'Int32Array': Int32Array ? Int32Array : Array,
'Uint32Array': Uint32Array ? Uint32Array : Array,
'Float32Array': Float32Array ? Float32Array : Array,
'Float64Array': Float64Array ? Float64Array : Array,
'DataView': DataView ? DataView : Array,
//ArrayBuffer
'Map': Map ? Map : Array,
'Set': Set ? Set : Array,
'Date': Date,
}
// and now reverse
var constructStrings = new pMap()//get a constructor string by it's consturctor
for (let k in constructors) {
if (constructors[k] === null) { continue; }
constructStrings.set(constructors[k], k)
}
constructStrings.set(Array, 'Array')
constructStrings.set(Error, 'Error')
//make conversion template underlying all object conversions
var conversionTemplate = {
out: function (p = {}) {
var { obj, clone, fork, useFilters, path } = p
for (var a in obj) {
if (!testFilters(path, obj, a, obj[a], useFilters)) { continue }
clone[a] = fork(obj[a], a, obj)
}
},
outJSON: function (p) {
var { clone } = p
this.out(p)
if (this.constructorType !== Object) { clone.__$_TYPE = constructStrings.get(this.constructorType) }
},
cast: function (p = {}) {
var { obj } = p;
return new (obj.constructor || this.constructorType)()
},
in: function (p = {}) { var { obj } = p; return obj },//
constructorType: Object,
}
//now populate conversion table
var conversion = {}
for (let k in constructors) {
conversion[k] = Object.assign(
{},
conversionTemplate,
{ constructorType: constructors[k] },
k.indexOf('Error') > -1 ? {
cast: function (p = {}) {
var { obj: err } = p
return Object.assign(new constructors[k](err.message, err.fileName, err.lineNumber), { name: err.name, columnNumber: err.columnNumber, stack: err.stack, })
},
out: () => { },
outJSON: function (p = {}) { var { obj, clone, fork } = p; Object.assign(clone, { __$_TYPE: k, message: obj.message, name: obj.name, fileName: obj.fileName, lineNumber: obj.lineNumber, columnNumber: obj.columnNumber, stack: obj.stack, }) },
in: function (p = {}) {
var { obj } = p
var replacement = new constructors[k](obj.message, obj.fileName, obj.lineNumber);
Object.assign(replacement, { columnNumber: obj.columnNumber, stack: obj.stack })
return replacement
}
} : {},
k.indexOf('Array') > -1 || k === 'DataView' ? {//pretty much for typed arrays.
cast: function (p = {}) {
var { obj: arr, registry } = p;
var useBuffer = registry.has(arr.buffer) ? registry.get(arr.buffer) : (() => { var newBuffer = arr.buffer.slice(); registry.set(arr.buffer, newBuffer); return newBuffer })()//hav I seen you before?
return new constructors[k](useBuffer, arr.byteOffset, arr.length)
},//for typed arrays.
out: function () { },//cast recreates because buffers and length are fixed at time of creation
outJSON: function (p = {}) {
var { obj: arr, clone, fork } = p
Object.assign(clone, { __$_TYPE: k, __$_LEN: arr.length, __$_byteOffset: arr.byteOffset, __$_buffer: fork(arr.buffer, 'buffer', arr) })
},
in: function (p = {}) {
var { obj: objArr, objPieces } = p//the buffer will have been recast first
return new this.constructorType(objPieces[objArr.__$_buffer._M], objArr.__$_byteOffset, objArr.__$_LEN)
},//for typed arrays
} : {}
)
}
var determineConversionSpace = function (obj) {
if (!obj) { obj = 'Object' }
if (typeof obj === 'string') { return conversion[obj] || conversionTemplate }
if (obj.constructor) { var cs = constructStrings.get(obj.constructor) }
return conversion[cs] || conversionTemplate
}
//modify
Object.assign(conversion, {
'Object': Object.assign(conversion['Object'], {
cast: function () { return {} }//for speed
}),
'Array': Object.assign(conversion['Array'], {// for speed
cast: function () { return [] },
out: function (p = {}) {
var { obj, clone, fork, useFilters, path } = p
for (let a = 0, len = obj.length; a < len; a++) {
if (!testFilters(path, obj, a, obj[a], useFilters)) { clone[a] = '[OMITTED]'; continue }
clone[a] = fork(obj[a], a, obj)
}
},
outJSON: function (p) {
var { obj: arr, clone } = p
this.out(p)
if (this.constructorType !== Object) { clone.__$_TYPE = constructStrings.get(this.constructorType); clone.__$_LEN = arr.length }
},
in: function (p = {}) {
var { obj: bluePrint } = p
var returnArray = []
for (let a = 0, len = bluePrint.__$_LEN; a < len; a++) {
returnArray[a] = bluePrint[a]
}
return returnArray
}
}),
'ArrayBuffer': Object.assign(conversion['ArrayBuffer'], {
cast: function (p = {}) { var { obj: arr } = p; return arr.slice() },
outJSON: function (p = {}) {
//determine if the buffer is a length of 2. if it's right, then just read from it into the string. if not, creat another buffer of the right size, read into it using an int8 view, and then read that into the string
var { obj, clone, fork } = p
var string = ''
var origLength = obj.byteLength
var even = origLength % 2 === 0
if (!even) {
var newLength = origLength + 1
var adjustedBuffer = new ArrayBuffer(newLength)
var readInView = new Uint8Array(adjustedBuffer)
var readOutView = new Uint8Array(obj)
for (let i = 0; i < readOutView.length; i++) {
readInView[i] = readOutView[i]
}
var view = new Uint16Array(adjustedBuffer)
}
else { var view = new Uint16Array(obj) }
for (let i = 0; i < view.length; i++) {
string += String.fromCharCode.call(null, view[i])
}
Object.assign(clone, { __$_ORIGLEN: origLength, byteLength: view.buffer.byteLength, __$_TYPE: 'ArrayBuffer', __$_VAL: string })
},
out: function () { },
in: function (p = {}) {
var { obj: bluePrint } = p
var buf = new ArrayBuffer(bluePrint.byteLength)
var str = bluePrint.__$_VAL
var bufView = new Uint16Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
var diff = buf.byteLength - bluePrint.__$_ORIGLEN
var ret = diff === 0 ? buf : buf.slice(0, buf.byteLength - 1)
return ret;
}
}),
'Map': Object.assign(conversion['Map'], {
out: function (p = {}) {
var { obj: map, clone, fork, useFilters, path } = p
var build = function (v, k) {
if (testFilters(path, map, k, v, useFilters)) {
var vclone = fork(v, k, map)//propagation is done here
var kclone = fork(k, k, map)
clone.set(kclone, vclone)//take results and populate new map
}
}
map.forEach(build)
},
outJSON: function (p = {}) {
var { obj: map, clone, fork, useFilters, path } = p
var counter = 0
var build = function (v, k) {
if (testFilters(path, map, k, v, useFilters)) {
var vclone = fork(v, k, map)//propagation is done here
var kclone = fork(k, k, map)
clone['_@-k' + counter] = kclone;
clone['_@-v' + counter] = vclone;
counter++
}
}
map.forEach(build)
clone.__$_TYPE = 'Map'; clone.__$_LEN = counter
},
in: function (p = {}) {
var { obj: objMap } = p
var replacement = new Map()
for (let i = 0; i < objMap.__$_LEN; i++) {
replacement.set(objMap['_@-k' + i], objMap['_@-v' + i])
delete (objMap['_@-v' + i])
delete (objMap['_@-k' + i])
}
return replacement
},
}),
'Set': Object.assign(conversion['Set'], {
out: function (p = {}) {
var { obj: set, clone, fork, useFilters, path } = p
var build = function (v) {
if (testFilters(path, set, v, v, useFilters)) {
var vclone = fork(v, v, set)//propagation is done here
clone.add(vclone)
}
}
set.forEach(build)
},
outJSON: function (p = {}) {
var { obj: set, clone, fork, useFilters, path } = p
var counter = 0
var build = function (v) {
if (testFilters(path, set, v, v, useFilters)) {
var vclone = fork(v, v, set)//propagation is done here
clone['_@-v' + counter++] = vclone
}
}
set.forEach(build)
clone.__$_TYPE = 'Set'; clone.__$_LEN = counter
},
in: function (p = {}) {
var { obj: objSet } = p
var replacement = new Set()
for (let i = 0; i < objSet.__$_LEN; i++) {
replacement.add(objSet['_@-v' + i])
delete (objSet['_@-v' + i])
}
return replacement
},
}),
'RegExp': Object.assign(conversion['RegExp'], {
cast: function (p = {}) { var { obj: reg, clone } = p; return new RegExp(reg) },
outJSON: function (p = {}) {
var { obj: reg, clone } = p
Object.assign(clone, { __$_VAL: reg.toString(), __$_TYPE: 'RegExp' })
},
out: () => { },
in: function (p = {}) {
var { obj: objReg, clone } = p
var val = objReg.__$_VAL.split('/')
val.shift()
var flags = val[val.length - 1]
val.pop()
return new RegExp(val.join('/'), flags)
}
}),
'Date': Object.assign(conversion['Date'], {
cast: function (p = {}) { var { obj: date } = p; return new Date(date.getTime()) },
out: () => { },
outJSON: function (p = {}) {
var { obj: date, clone } = p
Object.assign(clone, { __$_VAL: date.getTime(), __$_TYPE: 'Date' })
},
in: function (p = {}) {
var { obj: objDate } = p
return new Date(objDate.__$_VAL)
}
}),
'Function': Object.assign(conversion['Function'], {
cast: function (p = {}) {
var { obj: func, duplicateFuncs = false } = p
if (!duplicateFuncs) { return func }
return eval('(' + serialize(func) + ')')
},
out: () => { },//cast does it
outJSON: function (p = {}) {
var { obj: func, clone, fork } = p
Object.assign(clone, { __$_VAL: serialize(func), __$_TYPE: 'Function' })
},
in: function (p = {}) {
var { obj, fork } = p;
var val = obj.__$_VAL; delete obj.__$_VAL; delete obj.__$_TYPE;
var ret = eval('(' + val + ')')
return ret
}
}),
})