153 lines
5.3 KiB
JavaScript
153 lines
5.3 KiB
JavaScript
function serialize(input) {
|
|||
|
|
if (typeof input === "string") return `'${input}'`;
|
||
|
|
return new Serializer().serialize(input);
|
||
|
|
}
|
||
|
|
const asciiOrder = " _-,;:!?.'\"()[]{}@*/\\&#%`^+<=>|~$0123456789abcdefghijklmnopqrstuvwxyz";
|
||
|
|
const asciiWeights = /*@__PURE__*/ (function() {
|
||
|
|
const weights = /* @__PURE__ */ new Uint8Array(128);
|
||
|
|
for (let i = 0; i < 69; i++) weights[asciiOrder.charCodeAt(i)] = i + 1;
|
||
|
|
for (let code = 65; code <= 90; code++) weights[code] = weights[code + 32];
|
||
|
|
return weights;
|
||
|
|
})();
|
||
|
|
function compareStrings(a, b) {
|
||
|
|
if (a === b) return 0;
|
||
|
|
const length = Math.min(a.length, b.length);
|
||
|
|
let tieBreaker = 0;
|
||
|
|
for (let i = 0; i < length; i++) {
|
||
|
|
const codeA = a.charCodeAt(i);
|
||
|
|
const codeB = b.charCodeAt(i);
|
||
|
|
if (codeA === codeB) continue;
|
||
|
|
const weightA = codeA < 128 && asciiWeights[codeA] ? asciiWeights[codeA] : codeA + 128;
|
||
|
|
const weightB = codeB < 128 && asciiWeights[codeB] ? asciiWeights[codeB] : codeB + 128;
|
||
|
|
if (weightA !== weightB) return weightA < weightB ? -1 : 1;
|
||
|
|
if (tieBreaker === 0) tieBreaker = codeA > codeB ? -1 : 1;
|
||
|
|
}
|
||
|
|
if (a.length !== b.length) return a.length < b.length ? -1 : 1;
|
||
|
|
return tieBreaker;
|
||
|
|
}
|
||
|
|
const Serializer = /*@__PURE__*/ (function() {
|
||
|
|
class Serializer {
|
||
|
|
#context = /* @__PURE__ */ new Map();
|
||
|
|
compare(a, b) {
|
||
|
|
const typeA = typeof a;
|
||
|
|
const typeB = typeof b;
|
||
|
|
if (typeA === "string" && typeB === "string") return compareStrings(a, b);
|
||
|
|
if (typeA === "number" && typeB === "number") return a - b;
|
||
|
|
return compareStrings(this.serialize(a, true), this.serialize(b, true));
|
||
|
|
}
|
||
|
|
serialize(value, noQuotes) {
|
||
|
|
if (value === null) return "null";
|
||
|
|
switch (typeof value) {
|
||
|
|
case "string": return noQuotes ? value : `'${value}'`;
|
||
|
|
case "bigint": return `${value}n`;
|
||
|
|
case "object": return this.$object(value);
|
||
|
|
case "function": return this.$function(value);
|
||
|
|
}
|
||
|
|
return String(value);
|
||
|
|
}
|
||
|
|
serializeObject(object) {
|
||
|
|
const objString = Object.prototype.toString.call(object);
|
||
|
|
if (objString !== "[object Object]") return this.serializeBuiltInType(objString.length < 10 ? `unknown:${objString}` : objString.slice(8, -1), object);
|
||
|
|
const constructor = object.constructor;
|
||
|
|
const objName = constructor === Object || constructor === void 0 ? "" : constructor.name;
|
||
|
|
if (objName !== "" && globalThis[objName] === constructor) return this.serializeBuiltInType(objName, object);
|
||
|
|
if ("toJSON" in object && typeof object.toJSON === "function") {
|
||
|
|
const json = object.toJSON();
|
||
|
|
return objName + (json !== null && typeof json === "object" ? this.$object(json) : `(${this.serialize(json)})`);
|
||
|
|
}
|
||
|
|
const keys = Object.keys(object).sort(compareStrings);
|
||
|
|
let content = `${objName}{`;
|
||
|
|
for (let i = 0; i < keys.length; i++) {
|
||
|
|
const key = keys[i];
|
||
|
|
content += `${key}:${this.serialize(object[key])}`;
|
||
|
|
if (i < keys.length - 1) content += ",";
|
||
|
|
}
|
||
|
|
return content + "}";
|
||
|
|
}
|
||
|
|
serializeBuiltInType(type, object) {
|
||
|
|
const handler = this["$" + type];
|
||
|
|
if (handler) return handler.call(this, object);
|
||
|
|
if (typeof object.entries === "function") return this.serializeObjectEntries(type, object.entries());
|
||
|
|
throw new Error(`Cannot serialize ${type}`);
|
||
|
|
}
|
||
|
|
serializeObjectEntries(type, entries) {
|
||
|
|
const sortedEntries = Array.from(entries).sort((a, b) => this.compare(a[0], b[0]));
|
||
|
|
let content = `${type}{`;
|
||
|
|
for (let i = 0; i < sortedEntries.length; i++) {
|
||
|
|
const [key, value] = sortedEntries[i];
|
||
|
|
content += `${this.serialize(key, true)}:${this.serialize(value)}`;
|
||
|
|
if (i < sortedEntries.length - 1) content += ",";
|
||
|
|
}
|
||
|
|
return content + "}";
|
||
|
|
}
|
||
|
|
$object(object) {
|
||
|
|
let content = this.#context.get(object);
|
||
|
|
if (content === void 0) {
|
||
|
|
this.#context.set(object, `#${this.#context.size}`);
|
||
|
|
content = this.serializeObject(object);
|
||
|
|
this.#context.set(object, content);
|
||
|
|
}
|
||
|
|
return content;
|
||
|
|
}
|
||
|
|
$function(fn) {
|
||
|
|
const fnStr = Function.prototype.toString.call(fn);
|
||
|
|
if (fnStr.slice(-15) === "[native code] }") return `${fn.name || ""}()[native]`;
|
||
|
|
return `${fn.name}(${fn.length})${fnStr.replace(/\s*\n\s*/g, "")}`;
|
||
|
|
}
|
||
|
|
$Array(arr) {
|
||
|
|
let content = "[";
|
||
|
|
for (let i = 0; i < arr.length; i++) {
|
||
|
|
content += this.serialize(arr[i]);
|
||
|
|
if (i < arr.length - 1) content += ",";
|
||
|
|
}
|
||
|
|
return content + "]";
|
||
|
|
}
|
||
|
|
$Date(date) {
|
||
|
|
try {
|
||
|
|
return `Date(${date.toISOString()})`;
|
||
|
|
} catch {
|
||
|
|
return `Date(null)`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$ArrayBuffer(arr) {
|
||
|
|
return `ArrayBuffer[${new Uint8Array(arr).join(",")}]`;
|
||
|
|
}
|
||
|
|
$Set(set) {
|
||
|
|
return `Set${this.$Array(Array.from(set).sort((a, b) => this.compare(a, b)))}`;
|
||
|
|
}
|
||
|
|
$Map(map) {
|
||
|
|
return this.serializeObjectEntries("Map", map.entries());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const type of [
|
||
|
|
"Error",
|
||
|
|
"RegExp",
|
||
|
|
"URL"
|
||
|
|
]) Serializer.prototype["$" + type] = function(val) {
|
||
|
|
return `${type}(${val})`;
|
||
|
|
};
|
||
|
|
for (const type of [
|
||
|
|
"Int8Array",
|
||
|
|
"Uint8Array",
|
||
|
|
"Uint8ClampedArray",
|
||
|
|
"Int16Array",
|
||
|
|
"Uint16Array",
|
||
|
|
"Int32Array",
|
||
|
|
"Uint32Array",
|
||
|
|
"Float32Array",
|
||
|
|
"Float64Array"
|
||
|
|
]) Serializer.prototype["$" + type] = function(arr) {
|
||
|
|
return `${type}[${arr.join(",")}]`;
|
||
|
|
};
|
||
|
|
for (const type of ["BigInt64Array", "BigUint64Array"]) Serializer.prototype["$" + type] = function(arr) {
|
||
|
|
return `${type}[${arr.join("n,")}${arr.length > 0 ? "n" : ""}]`;
|
||
|
|
};
|
||
|
|
return Serializer;
|
||
|
|
})();
|
||
|
|
function isEqual(object1, object2) {
|
||
|
|
if (object1 === object2) return true;
|
||
|
|
if (serialize(object1) === serialize(object2)) return true;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
export { isEqual, serialize };
|