|
|
import { decode64 } from './base64.js'; |
|
|
import { |
|
|
HOLE, |
|
|
NAN, |
|
|
NEGATIVE_INFINITY, |
|
|
NEGATIVE_ZERO, |
|
|
POSITIVE_INFINITY, |
|
|
UNDEFINED |
|
|
} from './constants.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function parse(serialized, revivers) { |
|
|
return unflatten(JSON.parse(serialized), revivers); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function unflatten(parsed, revivers) { |
|
|
if (typeof parsed === 'number') return hydrate(parsed, true); |
|
|
|
|
|
if (!Array.isArray(parsed) || parsed.length === 0) { |
|
|
throw new Error('Invalid input'); |
|
|
} |
|
|
|
|
|
const values = (parsed); |
|
|
|
|
|
const hydrated = Array(values.length); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function hydrate(index, standalone = false) { |
|
|
if (index === UNDEFINED) return undefined; |
|
|
if (index === NAN) return NaN; |
|
|
if (index === POSITIVE_INFINITY) return Infinity; |
|
|
if (index === NEGATIVE_INFINITY) return -Infinity; |
|
|
if (index === NEGATIVE_ZERO) return -0; |
|
|
|
|
|
if (standalone || typeof index !== 'number') { |
|
|
throw new Error(`Invalid input`); |
|
|
} |
|
|
|
|
|
if (index in hydrated) return hydrated[index]; |
|
|
|
|
|
const value = values[index]; |
|
|
|
|
|
if (!value || typeof value !== 'object') { |
|
|
hydrated[index] = value; |
|
|
} else if (Array.isArray(value)) { |
|
|
if (typeof value[0] === 'string') { |
|
|
const type = value[0]; |
|
|
|
|
|
const reviver = revivers?.[type]; |
|
|
if (reviver) { |
|
|
let i = value[1]; |
|
|
if (typeof i !== 'number') { |
|
|
|
|
|
|
|
|
i = values.push(value[1]) - 1; |
|
|
} |
|
|
return (hydrated[index] = reviver(hydrate(i))); |
|
|
} |
|
|
|
|
|
switch (type) { |
|
|
case 'Date': |
|
|
hydrated[index] = new Date(value[1]); |
|
|
break; |
|
|
|
|
|
case 'Set': |
|
|
const set = new Set(); |
|
|
hydrated[index] = set; |
|
|
for (let i = 1; i < value.length; i += 1) { |
|
|
set.add(hydrate(value[i])); |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'Map': |
|
|
const map = new Map(); |
|
|
hydrated[index] = map; |
|
|
for (let i = 1; i < value.length; i += 2) { |
|
|
map.set(hydrate(value[i]), hydrate(value[i + 1])); |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'RegExp': |
|
|
hydrated[index] = new RegExp(value[1], value[2]); |
|
|
break; |
|
|
|
|
|
case 'Object': |
|
|
hydrated[index] = Object(value[1]); |
|
|
break; |
|
|
|
|
|
case 'BigInt': |
|
|
hydrated[index] = BigInt(value[1]); |
|
|
break; |
|
|
|
|
|
case 'null': |
|
|
const obj = Object.create(null); |
|
|
hydrated[index] = obj; |
|
|
for (let i = 1; i < value.length; i += 2) { |
|
|
obj[value[i]] = hydrate(value[i + 1]); |
|
|
} |
|
|
break; |
|
|
|
|
|
case 'Int8Array': |
|
|
case 'Uint8Array': |
|
|
case 'Uint8ClampedArray': |
|
|
case 'Int16Array': |
|
|
case 'Uint16Array': |
|
|
case 'Int32Array': |
|
|
case 'Uint32Array': |
|
|
case 'Float32Array': |
|
|
case 'Float64Array': |
|
|
case 'BigInt64Array': |
|
|
case 'BigUint64Array': { |
|
|
const TypedArrayConstructor = globalThis[type]; |
|
|
const typedArray = new TypedArrayConstructor(hydrate(value[1])); |
|
|
|
|
|
hydrated[index] = |
|
|
value[2] !== undefined |
|
|
? typedArray.subarray(value[2], value[3]) |
|
|
: typedArray; |
|
|
|
|
|
break; |
|
|
} |
|
|
|
|
|
case 'ArrayBuffer': { |
|
|
const base64 = value[1]; |
|
|
const arraybuffer = decode64(base64); |
|
|
hydrated[index] = arraybuffer; |
|
|
break; |
|
|
} |
|
|
|
|
|
case 'Temporal.Duration': |
|
|
case 'Temporal.Instant': |
|
|
case 'Temporal.PlainDate': |
|
|
case 'Temporal.PlainTime': |
|
|
case 'Temporal.PlainDateTime': |
|
|
case 'Temporal.PlainMonthDay': |
|
|
case 'Temporal.PlainYearMonth': |
|
|
case 'Temporal.ZonedDateTime': { |
|
|
const temporalName = type.slice(9); |
|
|
|
|
|
hydrated[index] = Temporal[temporalName].from(value[1]); |
|
|
break; |
|
|
} |
|
|
|
|
|
case 'URL': { |
|
|
const url = new URL(value[1]); |
|
|
hydrated[index] = url; |
|
|
break; |
|
|
} |
|
|
|
|
|
case 'URLSearchParams': { |
|
|
const url = new URLSearchParams(value[1]); |
|
|
hydrated[index] = url; |
|
|
break; |
|
|
} |
|
|
|
|
|
default: |
|
|
throw new Error(`Unknown type ${type}`); |
|
|
} |
|
|
} else { |
|
|
const array = new Array(value.length); |
|
|
hydrated[index] = array; |
|
|
|
|
|
for (let i = 0; i < value.length; i += 1) { |
|
|
const n = value[i]; |
|
|
if (n === HOLE) continue; |
|
|
|
|
|
array[i] = hydrate(n); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
|
|
|
const object = {}; |
|
|
hydrated[index] = object; |
|
|
|
|
|
for (const key in value) { |
|
|
if (key === '__proto__') { |
|
|
throw new Error('Cannot parse an object with a `__proto__` property'); |
|
|
} |
|
|
|
|
|
const n = value[key]; |
|
|
object[key] = hydrate(n); |
|
|
} |
|
|
} |
|
|
|
|
|
return hydrated[index]; |
|
|
} |
|
|
|
|
|
return hydrate(0); |
|
|
} |
|
|
|