@rickyli79/rich-json
Version:
Serialize and Deserialize javascript objects
165 lines (164 loc) • 5.24 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.stringify = void 0;
const lib_1 = require("./lib");
const MARK = Symbol('mark');
const IGNORE = Symbol('ignore');
let NATIVE_MARK;
let nativeKeys;
let usedMark;
let buildinSerializers;
function createRichValue(s) {
if (s.content !== null && typeof s.content === 'object') {
if (s.serializContent) {
s.content[MARK] = s.raw;
}
else {
s.content[IGNORE] = s.raw;
}
}
return {
[MARK]: MARK,
[NATIVE_MARK]: 1,
[nativeKeys.type]: s.type,
[nativeKeys.className]: s.className,
[nativeKeys.content]: s.content,
};
}
function isNativeProperty(value) {
return typeof value === 'object' && value?.[MARK] === MARK;
}
function toRichRef(ref) {
return createRichValue({
raw: undefined,
type: '$ref',
content: ref,
serializContent: false,
});
}
function toRichClass(value, serializer) {
const className = serializer.className;
let isPlainContent = false;
const content = serializer.toContent(value, () => {
isPlainContent = true;
});
if (isPlainContent) {
return content;
}
return createRichValue({
raw: value,
className,
type: 'Class',
content,
serializContent: serializer.serializContent,
});
}
function nativeReplacer(key, value, map, fullpath) {
if (value === null || value === undefined || typeof value === 'string')
return value;
const boxedValue = Object(value);
if (value === boxedValue)
map.set(value, fullpath);
(0, lib_1.calMarkId)(value, nativeKeys, usedMark);
for (const i in lib_1.customerSerializers) {
const iConverter = lib_1.customerSerializers[i];
if (iConverter.isType(value)) {
return toRichClass(value, iConverter);
}
}
for (const i in buildinSerializers) {
const iConverter = buildinSerializers[i];
if (iConverter.isType(boxedValue)) {
return toRichClass(boxedValue, iConverter);
}
}
return value;
}
function stringify(value, options) {
const { pretty, excludKeys = [] } = options ?? {};
const _excludKeys = Array.isArray(excludKeys) ? excludKeys : [excludKeys];
const root = value;
const map = new Map();
const toJsonMap = new Map();
NATIVE_MARK = '-MARK-' + Math.random() + Date.now() + Math.random();
const SP = '#';
nativeKeys = (0, lib_1.getNativeKeys)(SP);
usedMark = [];
try {
buildinSerializers = (0, lib_1.getBuildinSerializers)();
buildinSerializers.forEach(iSerializer => {
toJsonMap.set(iSerializer.class, iSerializer.class.prototype.toJSON);
iSerializer.class.prototype.toJSON = function () {
return this;
};
});
if (lib_1.isNodeJs) {
toJsonMap.set(Buffer, Buffer.prototype.toJSON);
Buffer.prototype.toJSON = function () {
return this;
};
}
lib_1.customerSerializers.forEach(iSerializer => {
if (!toJsonMap.has(iSerializer.class)) {
toJsonMap.set(iSerializer.class, iSerializer.class.prototype.toJSON);
}
iSerializer.class.prototype.toJSON = function () {
return this;
};
});
const wrap = {
[MARK]: MARK,
MARK: NATIVE_MARK,
SP,
root,
};
let re = JSON.stringify(wrap, function (key, value) {
if (value === wrap)
return value;
const include = this[MARK] ?? true;
if (!include)
return;
if (value === undefined)
return;
if (_excludKeys.some(i => {
if (i instanceof RegExp) {
return i.test(key);
}
return i === key;
})) {
return;
}
if (value === null || typeof value === 'string')
return value;
let fullpath;
const hasMark = isNativeProperty(this);
if (Object.is(value, root)) {
fullpath = ['$'];
}
else if (!hasMark && !this[IGNORE]) {
const parentJp = map.get(this[MARK] ?? this);
fullpath = [...parentJp, key];
}
if (!hasMark && map.has(value)) {
return toRichRef(map.get(value));
}
return nativeReplacer.call(this, key, value, map, fullpath);
}, pretty);
for (const [clazz, toJSON] of toJsonMap.entries()) {
clazz.prototype.toJSON = toJSON;
}
let nextMarkId = 0;
let markKey;
do {
markKey = (0, lib_1.getMarkKey)(nextMarkId++, nativeKeys.sp);
} while (usedMark.includes(markKey));
re = re.replace(new RegExp('"' + NATIVE_MARK + '"', 'g'), '"' + markKey + '"');
return re;
}
finally {
usedMark = [];
toJsonMap.clear();
map.clear();
}
}
exports.stringify = stringify;