UNPKG

defuss-dson

Version:

Typed serialization/deserialization for JavaScript datatypes (a superset of JSON).

239 lines (231 loc) 7.32 kB
'use strict'; const VOID = -1; const PRIMITIVE = 0; const ARRAY = 1; const OBJECT = 2; const DATE = 3; const REGEXP = 4; const MAP = 5; const SET = 6; const ERROR = 7; const BIGINT = 8; const env = typeof self === "object" ? self : globalThis; const deserializer = (input, result) => { const getValue = (out, index) => { input.set(index, out); return out; }; const decoder = (index) => { if (input.has(index)) return input.get(index); const [type, value] = result[index]; switch (type) { case PRIMITIVE: case VOID: return getValue(value, index); case ARRAY: { const arr = getValue([], index); for (const index2 of value) arr.push(decoder(index2)); return arr; } case OBJECT: { const object = getValue({}, index); for (const [key, index2] of value) object[decoder(key)] = decoder(index2); return object; } case DATE: return getValue(new Date(value), index); case REGEXP: { const { source, flags } = value; return getValue(new RegExp(source, flags), index); } case MAP: { const map = getValue(/* @__PURE__ */ new Map(), index); for (const [key, index2] of value) map.set(decoder(key), decoder(index2)); return map; } case SET: { const set = getValue(/* @__PURE__ */ new Set(), index); for (const index2 of value) set.add(decoder(index2)); return set; } case ERROR: { const { name, message } = value; try { return getValue(new env[name](message), index); } catch (e) { const error = new Error(message); error.name = name; return getValue(error, index); } } case BIGINT: return getValue(BigInt(value), index); case "BigInt": return getValue(Object(BigInt(value)), index); case "ArrayBuffer": return getValue(new Uint8Array(value).buffer, value); case "DataView": { const { buffer } = new Uint8Array(value); return getValue(new DataView(buffer), value); } } return getValue(new env[type](value), index); }; return decoder; }; const deserialize = (serialized) => deserializer(/* @__PURE__ */ new Map(), serialized)(0); const EMPTY = ""; const { toString: objectToString } = {}; const { keys: objectKeys } = Object; const getType = (value) => { const type = typeof value; if (type !== "object" || !value) return [PRIMITIVE, type]; const asString = objectToString.call(value).slice(8, -1); switch (asString) { case "Array": return [ARRAY, EMPTY]; case "Object": return [OBJECT, EMPTY]; case "Date": return [DATE, EMPTY]; case "RegExp": return [REGEXP, EMPTY]; case "Map": return [MAP, EMPTY]; case "Set": return [SET, EMPTY]; case "DataView": return [ARRAY, asString]; } if (asString.includes("Array")) return [ARRAY, asString]; if (asString.includes("Error")) return [ERROR, asString]; return [OBJECT, asString]; }; const isIgnoredType = ([TYPE, type]) => TYPE === PRIMITIVE && (type === "function" || type === "symbol"); const serializer = (strict, json, input, result) => { const getIndex = (out, value) => { const index = result.push(out) - 1; input.set(value, index); return index; }; const encoder = (value) => { if (input.has(value)) return input.get(value); let [TYPE, type] = getType(value); switch (TYPE) { case PRIMITIVE: { let entry = value; switch (type) { case "bigint": TYPE = BIGINT; entry = value.toString(); break; case "function": case "symbol": if (strict) throw new TypeError(`unable to serialize ${type}`); entry = null; break; /* c8 ignore stop */ case "undefined": return getIndex([VOID, void 0], value); } return getIndex([TYPE, entry], value); } case ARRAY: { if (type) { let spread = value; if (type === "DataView") { spread = new Uint8Array(value.buffer); } else if (type === "ArrayBuffer") { spread = new Uint8Array(value); } return getIndex([type, [...spread]], value); } const arr = []; const index = getIndex([TYPE, arr], value); for (const entry of value) arr.push(encoder(entry)); return index; } case OBJECT: { if (type) { switch (type) { case "BigInt": return getIndex([type, value.toString()], value); case "Boolean": case "Number": case "String": return getIndex([type, value.valueOf()], value); } } if (json && "toJSON" in value) return encoder(value.toJSON()); const entries = []; const index = getIndex([TYPE, entries], value); for (const key of objectKeys(value)) { if (strict || !isIgnoredType(getType(value[key]))) entries.push([encoder(key), encoder(value[key])]); } return index; } case DATE: return getIndex([TYPE, value.toISOString()], value); case REGEXP: { const { source, flags } = value; return getIndex([TYPE, { source, flags }], value); } case MAP: { const entries = []; const index = getIndex([TYPE, entries], value); for (const [key, entry] of value) { if (strict || !(isIgnoredType(getType(key)) || isIgnoredType(getType(entry)))) entries.push([encoder(key), encoder(entry)]); } return index; } case SET: { const entries = []; const index = getIndex([TYPE, entries], value); for (const entry of value) { if (strict || !isIgnoredType(getType(entry))) entries.push(encoder(entry)); } return index; } case ERROR: { const { name, message: message2 } = value; return getIndex([TYPE, { name, message: message2 }], value); } } const { message } = value; return getIndex([TYPE, { name: type, message }], value); }; return encoder; }; const serialize = (value, options = {}) => { const { json, lossy } = options; const result = []; serializer(!(json || lossy), !!json, /* @__PURE__ */ new Map(), result)(value); return result; }; const options = { json: true, lossy: true }; const parse = (str) => deserialize(JSON.parse(str)); const stringify = (any) => JSON.stringify(serialize(any, options)); function isEqual(objectA, objectB) { const serializedA = stringify(objectA); const normalizedA = parse(serializedA); const serializedB = stringify(objectB); const normalizedB = parse(serializedB); return stringify(normalizedA) === stringify(normalizedB); } const createClone = (value, options) => { return typeof structuredClone === "function" ? ( /* c8 ignore next */ structuredClone(value) ) : deserialize(serialize(value, options)); }; const clone = (value) => createClone(value, { json: false, lossy: false }); const DSON = { parse, stringify, isEqual, clone }; exports.DSON = DSON;