@badcafe/jsonizer
Version:
Structural reviving for JSON
1,213 lines (1,204 loc) • 44.5 kB
JavaScript
;
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;