UNPKG

@badcafe/jsonizer

Version:

Structural reviving for JSON

1,213 lines (1,204 loc) 44.5 kB
'use strict'; require('reflect-metadata'); const namespace = 'npm:@badcafe/jsonizer'; const Errors = {}; let NsService; function setNamespaceService(nsService) { NsService = nsService; } const IS_STRING = Symbol.for(`${namespace}.Class.IsString`); const CHILDREN = Symbol.for(`${namespace}.Class.Children`); exports.Class = void 0; (function (Class) { function rename(fun, name) { if (fun.name === name) { return fun; } else if (fun[IS_STRING]) { throw new TypeError(`${fun.name}'s name is locked and can't be renamed to "${name}"`); } else { const ns = Reflect.getMetadata(NsService.$, fun); if (ns !== undefined) { NsService('')(fun); } let children = []; if (fun.hasOwnProperty(CHILDREN)) { children = [...fun[CHILDREN]]; delete fun[CHILDREN]; } const wrapped = new Proxy(fun, { get(target, prop, receiver) { return prop === 'name' ? name : prop === CHILDREN ? children : Reflect.get(target, prop, receiver); }, construct(target, args) { const o = new target(...args); o.constructor = wrapped; return o; } }); for (const child of children) { Reflect.defineMetadata(NsService.$, wrapped, child); } if (ns !== undefined) { NsService(ns)(wrapped); } return wrapped; } } Class.rename = rename; })(exports.Class || (exports.Class = {})); function Namespace(ns) { return ((target) => { if (!target) { throw new TypeError('Missing Namespace target ; pass a class or see @badcafe/ts-plugin'); } unregisterClass(target); unregisterTree(target); if (typeof ns === 'string' && ns.length > 0) { ns = ns.split('.').reduce((parent, name) => { let cl = registry.get(name)?.find(cl => cl[IS_STRING]); if (!cl) { const clazz = { [ns]: class { } }[ns]; cl = exports.Class.rename(clazz, ns); cl[IS_STRING] = true; } if (parent) { parent.hasOwnProperty(CHILDREN) ? parent[CHILDREN].push(cl) : (parent[CHILDREN] = [cl]); } return cl; }, undefined); } if (typeof ns !== 'string') { ns.hasOwnProperty(CHILDREN) ? ns[CHILDREN].push(target) : (ns[CHILDREN] = [target]); } Reflect.defineMetadata(Namespace.$, ns, target); const qn = Namespace.getQualifiedName(target); registerClass(qn, target); registerTree(target); return target; }); } const REGISTRY = Symbol.for(`${namespace}.Namespace.Registry`); const registry = globalThis[REGISTRY] ?? (globalThis[REGISTRY] = new Map()); function unregisterTree(target) { for (const child of target.hasOwnProperty(CHILDREN) ? target[CHILDREN] : []) { unregisterClass(child); unregisterTree(child); } } function unregisterClass(target) { const qn = Namespace.getQualifiedName(target); const cl = registry.get(qn); if (cl) { const i = cl.findIndex(o => o === target); if (i !== -1) { if (cl.length === 1) { registry.delete(qn); } else { cl.splice(i, 1); } } } } function registerTree(target) { for (const child of target.hasOwnProperty(CHILDREN) ? target[CHILDREN] : []) { const qn = Namespace.getQualifiedName(child); registerClass(qn, child); registerTree(child); } } function registerClass(qn, target) { const classes = registry.get(qn); if (!classes) { registry.set(qn, [target]); } else if (!classes?.includes(target)) { classes?.push(target); } } (function (Namespace) { Namespace.$ = Symbol.for(`${namespace}.Namespace`); function hasNamespace(target) { return Reflect.hasMetadata(Namespace.$, target); } Namespace.hasNamespace = hasNamespace; function getQualifiedName(target) { const ns = Reflect.getMetadata(Namespace.$, target); if (typeof ns === 'string' && ns.length > 0) { return `${ns}.${target.name}`; } else if (ns) { return `${getQualifiedName(ns)}.${target.name}`; } else { const qname = target.name; return (Errors.isError(target) && !Namespace.hasClass(qname)) ? 'error.' + qname : qname; } } Namespace.getQualifiedName = getQualifiedName; function getClass(qname) { const cl = hasClass(qname); if (cl) { return cl; } else { const Err = Errors.getClass('Not Found', true, 404); throw new Err(`"${qname}" not found in registry`); } } Namespace.getClass = getClass; function hasClass(qname) { const cl = registry.get(qname); if (cl) { if (cl.length > 1) { const Err = Errors.getClass('Name Conflict', true, 409); throw new Err(`"${qname}" was registered ${cl.length === 2 ? 'twice' : `${cl.length} times`}. Consider declaring "Namespace()" on the classes.`); } return cl[0]; } else if (qname.startsWith('error.')) { return Error; } } Namespace.hasClass = hasClass; function dedup(qname) { const cl = registry.get(qname); if (cl && cl.length > 1) { cl.length = 1; } } Namespace.dedup = dedup; function* checkIntegrity(noFail = true) { for (const entry of registry) { if (entry[1].length > 1) { const err = new Error(`A qualified name must be bound to a single class, "${entry[0]}" is bound to ${entry[1].length} classes`); if (!noFail) { throw err; } console.error(err); yield [entry[0], ...entry[1]]; } yield [entry[0], entry[1][0]]; } } Namespace.checkIntegrity = checkIntegrity; function registerClassByQualifiedName(qn, target) { const pkg = qn.split('.').slice(0, -1).join('.'); if (pkg.length > 0) { const parent = Namespace.hasClass(pkg); if (parent) { Namespace(parent)(target); } else { Namespace(pkg)(target); } } else if (arguments[2]) { Reflect.defineMetadata(Namespace.$, '', target); registerClass(qn, target); } else { Namespace('')(target); } } Namespace.registerClassByQualifiedName = registerClassByQualifiedName; })(Namespace || (Namespace = {})); function unregisterClassTree(target) { unregisterClass(target); unregisterTree(target); registerTree(target); } Namespace.unregisterClass = unregisterClassTree; setNamespaceService(Namespace); function isPrimitive(value) { return value === null || value === undefined || value === true || value === false || typeof value === 'string' || typeof value === 'number'; } function valuedKeys(obj) { return Object.entries(obj) .filter(([, val]) => val !== undefined) .map(([key]) => key); } function deepEquals(a, b, missings) { if (a === b) { return true; } if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null) { return false; } const keysA = valuedKeys(a); const keysB = valuedKeys(b); let missingsProcessed = false; function processMissings() { if (!missingsProcessed) { missingsProcessed = true; const setA = new Set(keysA); const setB = new Set(keysB); for (const key of setA) { if (setB.has(key)) { setB.delete(key); } else { if (missings(key, a, b)) { keysB.push(key); setB.delete(key); } } } for (const key of setB) { if (missings(key, b, a)) { keysA.push(key); } } } } if (keysA.length !== keysB.length) { if (!Array.isArray(a) && missings) { processMissings(); if (keysA.length !== keysB.length) { return false; } } else { return false; } } for (const key of keysA) { if (!keysB.includes(key)) { if (!Array.isArray(a) && missings) { processMissings(); if (!keysB.includes(key)) { return false; } } else { return false; } } if (!deepEquals(a[key], b[key], missings)) { return false; } } return true; } exports.Errors = void 0; (function (Errors) { Errors.errors = { Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError }; const CODE = Symbol.for(`${namespace}.ErrorCode`); const classes = new Map(); function getClass(name, unique = true, code) { let Err = classes.get(name); if (Err) { if (code && !Err[CODE]) { Err[CODE] = code; } if (code === undefined || Err[CODE] === code) { return Err; } } Err = { [name]: class extends Error { constructor(message) { super(message); } } }[name]; Err = exports.Class.rename(Err, name); if (code !== undefined) { Err[CODE] = code; } if (unique) { classes.set(name, Err); } return Err; } Errors.getClass = getClass; function getCode(error) { const code = error[CODE]; if (code) { return code; } if (error instanceof Error) { error = error.constructor; } return error[CODE]; } Errors.getCode = getCode; function setCode(error, code) { if (error.constructor[CODE] !== code) { error[CODE] = code; } } Errors.setCode = setCode; function getName(error) { if (error instanceof Error) { error = error.constructor; } return error.name; } Errors.getName = getName; function isError(err) { return err ? err === Error ? true : isError(Object.getPrototypeOf(err)) : false; } Errors.isError = isError; function toString(err) { } Errors.toString = toString; })(exports.Errors || (exports.Errors = {})); function setErrorsToString(toString) { exports.Errors.toString = toString; } Object.assign(Errors, exports.Errors); for (const err in exports.Errors.errors) { const cl = exports.Errors.errors[err]; Namespace.registerClassByQualifiedName.apply(null, [err, cl, true]); } setErrorsToString(err => JSON.stringify(err, exports.Jsonizer.REPLACER) .slice(1, -1)); exports.Mappers = void 0; (function (Mappers) { (function (Jokers) { Jokers.$ = Symbol.for(`${namespace}.JokersKey`); })(Mappers.Jokers || (Mappers.Jokers = {})); (function (Matcher) { function getMatchingMapper(mappers, prop) { const delim = (mappers[Mappers.Jokers.$]?.[2] ?? typeof prop === 'string') ? '/' : '-'; if (typeof prop === 'string') { for (const [key, mapper] of Object.entries(mappers)) { if (key.length > 1 && key[0] === delim && key[key.length - 1] === delim && new RegExp(key.slice(1, -1)).test(prop)) { return mapper; } } } else { for (const [key, mapper] of Object.entries(mappers)) { const [from, to] = key.split(delim) .map(int => Number.parseInt(int)); if (from >= 0 && to > from && prop >= from && prop <= to) { return mapper; } } } } Matcher.getMatchingMapper = getMatchingMapper; function isRegExp(key, delim = '/') { return key.length > 1 && key[0] === delim && key[key.length - 1] === delim; } Matcher.isRegExp = isRegExp; function isRange(key, delim = '-') { return !isNaN(key.split(delim) .map(int => Number.parseInt(int)) .reduce((prev, current, _, arr) => arr.length === 2 && prev < current ? current : NaN, -1)); } Matcher.isRange = isRange; })(Mappers.Matcher || (Mappers.Matcher = {})); (function (Optimizable) { function keyInRange(keys, mapper, value, keyIndex) { let key = String(keyIndex); if (keys.length === 0) { keys.push({ from: keyIndex, key }); } else { const last = keys[keys.length - 1]; const previousKey = last.key; if (Math.max(last.from, last.to ?? 0) + 1 === keyIndex && mapper[previousKey] === value) { delete mapper[previousKey]; last.to = keyIndex; key = last.key = `${String(last.from)}-${String(last.to)}`; return [key, true]; } else { keys.push({ from: keyIndex, key }); } } return [key, false]; } Optimizable.keyInRange = keyInRange; function optimizeRanges(mappers) { let maybeTuple = true; if (mappers.keys.length > 1) { mappers.keys = mappers.keys.reduce((prev, curr) => { if (curr.to) { maybeTuple = false; } if (prev.length > 0) { const other = prev[prev.length - 1]; const mapper = mappers.mapper[curr.key]; function missings(key, mapperHas, mapperHasNot) { if (mapperHasNot[internal.Replacer.UNMAPPED_KEYS]?.has(key)) { return false; } const submapper = Mappers.Matcher.getMatchingMapper(mapperHasNot, Number(key)) ?? mapperHasNot['*']; if (submapper) { if (deepEquals(mapperHas[key], submapper, missings)) { mapperHasNot[key] = submapper; return true; } else { return false; } } else { mapperHasNot[key] = mapperHas[key]; return true; } } if (Math.max(other.from, other.to ?? 0) + 1 === curr.from && deepEquals(mapper, mappers.mapper[other.key], missings)) { for (const k of mappers.mapper[curr.key][internal.Replacer.UNMAPPED_KEYS]) { mapper[internal.Replacer.UNMAPPED_KEYS].add(k); } delete mappers.mapper[curr.key]; delete mappers.mapper[other.key]; mappers.count--; other.to = curr.to ?? curr.from; other.key = `${other.from}-${other.to}`; mappers.mapper[other.key] = mapper; maybeTuple = false; } else { prev.push(curr); } } else { prev.push(curr); } return prev; }, []); } if (mappers.keys.length > 0 && mappers.keys.length === mappers.count && (mappers.keys.length === 1 || !maybeTuple)) { const lastKey = mappers.keys[mappers.keys.length - 1].key; mappers.mapper['*'] = mappers.mapper[lastKey]; delete mappers.mapper[lastKey]; } } Optimizable.optimizeRanges = optimizeRanges; function pruneEmptyMappings(mappers) { for (const key in mappers) { const sub = mappers[key]; if (typeof sub === 'boolean') { delete mappers[key]; } else if (sub && typeof sub === 'object') { if (Object.keys(sub).length === 0) { delete mappers[key]; } else if (Object.keys(sub).length === 1 && typeof sub['.'] === 'string') { mappers[key] = sub['.']; } } } } Optimizable.pruneEmptyMappings = pruneEmptyMappings; })(Mappers.Optimizable || (Mappers.Optimizable = {})); })(exports.Mappers || (exports.Mappers = {})); function Reviver(mappers) { return ((target) => { if (!target) { throw new TypeError('Missing Reviver target ; pass a class or see @badcafe/ts-plugin'); } if (!Namespace.hasNamespace(target)) { Namespace('')(target); } Reflect.defineMetadata(Reviver.$, new internal.Reviver(mappers, target), target); return target; }); } (function (Reviver) { Reviver.$ = Symbol.for(`${namespace}.Reviver`); function get(target = internal.Reviver) { let rev = Reflect.getMetadata(Reviver.$, target); if (rev === false) { return undefined; } else if (!rev && arguments[1] === undefined && target instanceof Function && /^\(\) ?=>.*/.test(target.toString())) { try { rev = Reflect.getMetadata(Reviver.$, target()); } catch (e) { } finally { Reflect.defineMetadata(Reviver.$, target, rev ?? false); } } return rev; } Reviver.get = get; function revive(value) { return new internal.Reviver(value); } Reviver.revive = revive; })(Reviver || (Reviver = {})); exports.Jsonizer = void 0; (function (Jsonizer) { Jsonizer.NAMESPACE = namespace; Jsonizer.toJSON = Symbol.for(`${namespace}.toJSON`); function reviver(mappers) { return new internal.Reviver(mappers); } Jsonizer.reviver = reviver; function replacer() { return new internal.Replacer(); } Jsonizer.replacer = replacer; Jsonizer.REPLACER = function getJSON(key, value) { return value?.[Jsonizer.toJSON]?.() ?? value; }; (function (Self) { const IDENTITY = Symbol.for(`${namespace}.Self.Identity`); Self.Identity = globalThis[IDENTITY] ?? (globalThis[IDENTITY] = class Identity { constructor() { throw new TypeError('Creating an instance of Identity class is non sense'); } }); function apply(clazz) { return (args) => new clazz(...Object.values(args)); } Self.apply = apply; function assign(clazz) { return (args) => { const obj = new clazz(); Object.assign(obj, args); return obj; }; } Self.assign = assign; function endorse(clazz) { return (args) => { Object.setPrototypeOf(args, clazz.prototype); return args; }; } Self.endorse = endorse; })(Jsonizer.Self || (Jsonizer.Self = {})); })(exports.Jsonizer || (exports.Jsonizer = {})); const Mappers$ = Symbol.for(`${namespace}.Mappers`); const Clazz = Symbol.for(`${namespace}.Class`); var internal; (function (internal) { class Reviver extends Function { constructor(mappers, clazz) { super(); this[Mappers$] = mappers ?? {}; this[Clazz] = clazz; return new Proxy(this, { apply(target, thisArg, argArray) { if (argArray.length === 1 || (argArray.length == 3 && typeof argArray[1] === 'number' && Array.isArray(argArray[2]))) { const [json] = argArray; return json === undefined ? undefined : Reviver.revive([], json, target[Mappers$]); } else { const [key, value] = argArray; if (key === '') { return Reviver.revive([], value, target[Mappers$]); } else { return value; } } }, get(target, prop, receiver) { if (typeof prop === 'number' || (typeof prop === 'string' && prop !== 'toJSON')) { const [any, self] = Reflect.get(target, exports.Mappers.Jokers.$, receiver) ?? ['*', '.']; if (prop === any || prop === self) { return Reflect.get(target, Mappers$, receiver)[prop]; } else { const mappers = Reflect.get(target, internal.Reviver.$, receiver); const key = Number(prop); const submapper = mappers[prop] ?? (Number.isNaN(key) ? exports.Mappers.Matcher.getMatchingMapper(mappers, prop) : exports.Mappers.Matcher.getMatchingMapper(mappers, key)) ?? mappers[any] ?? {}; return exports.Jsonizer.reviver(typeof submapper === 'object' ? submapper : { '.': submapper }); } } else { return Reflect.get(target, prop, receiver); } }, set(target, prop, value, receiver) { if (typeof prop === 'number' || (typeof prop === 'string')) { return Reflect.get(target, Mappers$, receiver)[prop] = value; } else { return false; } }, ownKeys(target) { const mappers = Reflect.get(target, Mappers$); const ownKeys = new Set(Reflect.ownKeys(mappers)); ownKeys.add('arguments'); ownKeys.add('caller'); ownKeys.add('prototype'); return [...ownKeys.keys()]; }, getOwnPropertyDescriptor(target, prop) { return Reflect.getOwnPropertyDescriptor(target, prop) ?? { enumerable: true, configurable: true }; } }); } toJSON(key) { return this[Clazz] ? key === '' ? { '.': Namespace.getQualifiedName(this[Clazz]) } : Namespace.getQualifiedName(this[Clazz]) : internal.toJSON(this[Mappers$]); } static revive(ctxt, json, mappers) { if (mappers instanceof Reviver) { mappers = mappers[Mappers$]; } const stack = ctxt ? (ctxt.push(json), ctxt) : [json]; try { const isArray = Array.isArray(json); let [Any, Self, delim] = mappers[exports.Mappers.Jokers.$] ?? ['*', '.', isArray ? '-' : '/']; let selfMapper; let anyMapper; function resolveMapper(mapper, key) { if (typeof mapper === 'string') { mapper = Namespace.getClass(mapper); } if (typeof mapper === 'function') { const funMapper = ReviverAPI.get(mapper); if (funMapper) { mapper = funMapper; } } return mapper; } const matchMappers = []; function applyMatcherOrAny(prop) { const value = json[prop]; for (let [key, matcher, mapper] of matchMappers) { if (matcher.test(prop)) { mapper = resolveMapper(mapper, key); json[prop] = Reviver.revive(stack, value, mapper); return; } } if (anyMapper) { anyMapper = resolveMapper(anyMapper, Any); json[prop] = Reviver.revive(stack, value, anyMapper); } } const context = isArray ? { isArray, keys: new Set(), startAny: -1, restAny: -1, key: -1, i: -1, } : { isArray, keys: new Set(), }; let classMapper; const entries = { *[Symbol.iterator]() { yield* Object.entries(mappers); if (classMapper) { [Any, Self, delim] = classMapper[exports.Mappers.Jokers.$] ?? ['*', '.', isArray ? '-' : '/']; classMapper = classMapper[Mappers$]; yield* Object.entries(classMapper) .filter(([key]) => key !== Self); } } }; for (let [key, mapper] of entries) { if (key === Any) { anyMapper = mapper; } else if (key === Self) { if (typeof mapper === 'string') { mapper = Namespace.getClass(mapper); } if (typeof mapper === 'function') { classMapper = ReviverAPI.get(mapper, true); const itSelf = classMapper?.[exports.Mappers.Jokers.$]?.[1] ?? '.'; if (classMapper?.[itSelf]) { mapper = classMapper[itSelf]; if (mapper === exports.Jsonizer.Self.Identity) { mapper = undefined; } else { mappers = mapper; } } else if (classMapper) { mapper = undefined; } else { classMapper = undefined; } } selfMapper = mapper; } else if (!context.isArray && exports.Mappers.Matcher.isRegExp(key, delim)) { matchMappers.push([ key, new RegExp(key.slice(1, -1)), mapper ]); } else if (context.isArray && exports.Mappers.Matcher.isRange(key, delim)) { const [from, to] = key.split(delim) .map(int => Number.parseInt(int)); matchMappers.push([ key, { test: (i) => i >= from && i <= to }, mapper ]); } else if (mapper) { if (context.isArray) { const n = Math.abs(Number.parseInt(key)); if (Number.isNaN(n)) { } else { context.key = n; context.i++; if (context.key === context.i && context.startAny === context.i) { context.startAny = context.i + 1; } else { context.keys.add(context.key); } context.restAny = Math.max(context.restAny, context.key); } } else { context.keys.add(key); } mapper = resolveMapper(mapper, key); if (context.isArray) { const i = Number.parseInt(key); const value = json[i]; if (value !== undefined) { json[i] = Reviver.revive(stack, value, mapper); } } else if (json && typeof json === 'object') { const value = json[key]; if (value !== undefined) { json[key] = Reviver.revive(stack, value, mapper); } } } } if (anyMapper || matchMappers.length > 0) { if (context.isArray) { context.startAny = Math.max(context.startAny, 0); context.restAny = Math.max(context.restAny, 0); for (let i = context.startAny; i < context.restAny; i++) { if (!context.keys.has(i)) { applyMatcherOrAny(i); } } for (let i = context.restAny; i < json.length; i++) { applyMatcherOrAny(i); } } else if (json && typeof json === 'object') { for (const key in json) { if ((!context.keys.has(key))) { applyMatcherOrAny(key); } } } } if (selfMapper) { if (!Reflect.getMetadata(Reviver.$, selfMapper)) { return selfMapper.call(stack, json); } } return json; } finally { stack.pop(); } } } Reviver.$ = Mappers$; internal.Reviver = Reviver; function toJSON(mappers, recurse = true) { return Object.fromEntries(Object.entries(mappers ?? {}) .filter(([key, mapper]) => typeof mapper === 'string' || (typeof mapper === 'function' && (mapper[Mappers$] || ReviverAPI.get(mapper))) || typeof mapper === 'object') .map(([key, mapper]) => [key, typeof mapper === 'string' ? mapper : mapper instanceof Reviver ? mapper : typeof mapper === 'function' && ReviverAPI.get(mapper) ? Namespace.getQualifiedName(mapper) : recurse ? internal.toJSON(mapper) : mapper ])); } internal.toJSON = toJSON; class Replacer extends Function { init(value) { this.pushContext({ size: 1, isArray: false, before: { '': value }, mapper: {}, key: '', count: 0, keys: [] }); } constructor() { super(); this.end = false; this.stack = []; this.stringify = false; return new Proxy(this, { apply(target, thisArg, argArray) { const [key, value] = argArray; const directCall = target.stack.length === 0; if (directCall) { target.init(value); target.end = true; } const res = target.replace(key, value); if (directCall) { target.end = true; } return res; } }); } getReviver() { return this.isEmpty() ? null : exports.Jsonizer.reviver(this.stack[0].mapper); } getMappers() { return this.isEmpty() ? null : this.stack[0].mapper; } isEmpty() { if (!this.end) { const Err = exports.Errors.getClass('Illegal Access', true); throw new Err('This instance of replacer wasn\'t yet used in JSON.stringify()'); } return this.stack.length === 0 || Object.keys(this.stack[0].mapper).length === 0; } static getReviverClass(value) { const clazz = value?.constructor; return clazz && clazz !== Object && clazz !== Array && ReviverAPI.get(clazz) ? Namespace.getQualifiedName(clazz) : undefined; } setMapper(current, key, reviver, value) { if (!reviver) { current.mapper[Replacer.UNMAPPED_KEYS].add(String(key)); } if (reviver) { if (current.isArray) { let inRange; [key, inRange] = exports.Mappers.Optimizable.keyInRange(current.keys, current.mapper, reviver, key); if (!inRange) { current.count++; } } current.mapper[key] = reviver; } else if (current.isArray) { current.count++; if (!isPrimitive(value)) { current.keys.push({ from: key, key: key = String(key) }); } } } pushContext(context) { const parent = this.stack[this.stack.length - 1]; if (parent) { context.parent = parent; } if (context.mapper) { context.mapper[Replacer.UNMAPPED_KEYS] = new Set(); } this.stack.push(context); if (parent && context.mapper && parent.mapper !== context.mapper) { parent.mapper[context.key] = context.mapper; } return context; } replace(key, value) { const context = this.stack[this.stack.length - 1]; try { const before = context.before[key]; let after = before === null || before === undefined || before === true || before === false ? value : before[exports.Jsonizer.toJSON]?.() ?? (this.stringify || value instanceof Date ? value : value?.toJSON?.() ?? value); const [size, isArray] = Array.isArray(after) ? [after.length, true] : after && typeof after === 'object' ? [Object.keys(after).length, false] : [0]; let reviver; if (context.mapper && typeof context.mapper === 'object') { reviver = Replacer.getReviverClass(before); this.setMapper(context, context.parent ? context.isArray ? Number(key) : key : '.', reviver, before); } if (size > 0) { const mapper = reviver === undefined && context.mapper && typeof context.mapper === 'object' ? context.parent === undefined ? context.mapper : {} : undefined; if (!this.stringify && after === value) { if (isArray) { after = [...after]; } else { after = Object.assign({}, after); } } this.pushContext({ size, isArray, before: after, mapper, key, count: 0, keys: [] }); if (!this.stringify) { if (isArray) { after.forEach((v, i) => { after[i] = this.replace(String(i), v); }); } else { for (const key in after) { after[key] = this.replace(key, after[key]); } } } } return after; } finally { context.size--; this.unStack(); } } unStack() { const top = this.stack[this.stack.length - 1]; if (top.size === 0) { if (top.isArray) { exports.Mappers.Optimizable.optimizeRanges(top); } exports.Mappers.Optimizable.pruneEmptyMappings(top.mapper); if (this.stack.length > 1) { this.stack.pop(); this.unStack(); } } } toString() { return (this.isEmpty() ? null : this.stack[0].mapper && JSON.stringify(internal.toJSON(this.stack[0].mapper))); } } internal.Replacer = Replacer; (function (Replacer) { Replacer.UNMAPPED_KEYS = Symbol.for(`${namespace}.UnmappedKeys`); })(Replacer = internal.Replacer || (internal.Replacer = {})); })(internal || (internal = {})); const ReviverAPI = Reviver; function stringify(value, replacer, space) { if (replacer instanceof internal.Reviver) { throw new TypeError('Unable to stringify with a reviver ; please pass a replacer instead'); } if (replacer instanceof internal.Replacer && replacer.end) { const Err = exports.Errors.getClass('Precondition Failed', true, 412); throw new Err('This instance of replacer was already used in JSON.stringify(), please create a new one with Jsonizer.replacer()'); } replacer = replacer === exports.Jsonizer.REPLACER ? exports.Jsonizer.replacer() : replacer; try { if (replacer instanceof internal.Replacer) { replacer.init(value); replacer.stringify = true; } return jsonStringify(value, replacer, space); } finally { if (replacer instanceof internal.Replacer) { replacer.end = true; } } } const jsonStringify = JSON.stringify; try { JSON.stringify = stringify; } catch (err) { console.warn('Unable to patch JSON.stringify(), use stringify() instead', err); } Namespace(exports.Jsonizer.NAMESPACE)(internal.Reviver); Reviver({ '.': mappers => new internal.Reviver(mappers) })(internal.Reviver); Namespace.dedup(Namespace.getQualifiedName(internal.Reviver)); Namespace(exports.Jsonizer.NAMESPACE)(internal.Replacer); Namespace.dedup(Namespace.getQualifiedName(internal.Replacer)); Namespace(exports.Jsonizer.NAMESPACE)(exports.Jsonizer.Self.Identity); Reviver({ '.': any => any })(exports.Jsonizer.Self.Identity); Namespace.dedup(Namespace.getQualifiedName(exports.Jsonizer.Self.Identity)); Reviver({ '.': date => date ? new Date(date) : null })(Date); function getErrConstructor(name) { const Err = exports.Errors.errors[name]; if (Err) { return Err; } try { const cl = Namespace.getClass(name); return cl === Error ? exports.Errors.getClass(name) : cl; } catch (e) { return exports.Errors.getClass(name); } } Reviver({ '.': message => { if (message === null) { return null; } let name = /(\w(?:\s|\w)*):\s*(.*)/.exec(message); if (name?.[1]) { [, name, message] = name; } const Err = typeof name === 'string' ? getErrConstructor(name.trim()) : Error; let err; try { err = new Err(message); } catch (e) { err = new (typeof name === 'string' ? exports.Errors.getClass(name.trim()) : Error)(message); } return err; } })(Error); Error.prototype[exports.Jsonizer.toJSON] = function () { return 'constructor' in this ? this.message === '' ? exports.Errors.getName(this.constructor) : `${exports.Errors.getName(this.constructor)}: ${this.message}` : this.toString(); }; Reviver({ '.': re => re ? new RegExp(re.slice(1, -1)) : null })(RegExp); RegExp.prototype[exports.Jsonizer.toJSON] = RegExp.prototype.toString; exports.Namespace = Namespace; exports.Reviver = Reviver; exports.stringify = stringify;