|
import { |
|
VOID, PRIMITIVE, |
|
ARRAY, OBJECT, |
|
DATE, REGEXP, MAP, SET, |
|
ERROR, BIGINT |
|
} from './types.js'; |
|
|
|
const EMPTY = ''; |
|
|
|
const {toString} = {}; |
|
const {keys} = Object; |
|
|
|
const typeOf = value => { |
|
const type = typeof value; |
|
if (type !== 'object' || !value) |
|
return [PRIMITIVE, type]; |
|
|
|
const asString = toString.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]; |
|
} |
|
|
|
if (asString.includes('Array')) |
|
return [ARRAY, asString]; |
|
|
|
if (asString.includes('Error')) |
|
return [ERROR, asString]; |
|
|
|
return [OBJECT, asString]; |
|
}; |
|
|
|
const shouldSkip = ([TYPE, type]) => ( |
|
TYPE === PRIMITIVE && |
|
(type === 'function' || type === 'symbol') |
|
); |
|
|
|
const serializer = (strict, json, $, _) => { |
|
|
|
const as = (out, value) => { |
|
const index = _.push(out) - 1; |
|
$.set(value, index); |
|
return index; |
|
}; |
|
|
|
const pair = value => { |
|
if ($.has(value)) |
|
return $.get(value); |
|
|
|
let [TYPE, type] = typeOf(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; |
|
case 'undefined': |
|
return as([VOID], value); |
|
} |
|
return as([TYPE, entry], value); |
|
} |
|
case ARRAY: { |
|
if (type) |
|
return as([type, [...value]], value); |
|
|
|
const arr = []; |
|
const index = as([TYPE, arr], value); |
|
for (const entry of value) |
|
arr.push(pair(entry)); |
|
return index; |
|
} |
|
case OBJECT: { |
|
if (type) { |
|
switch (type) { |
|
case 'BigInt': |
|
return as([type, value.toString()], value); |
|
case 'Boolean': |
|
case 'Number': |
|
case 'String': |
|
return as([type, value.valueOf()], value); |
|
} |
|
} |
|
|
|
if (json && ('toJSON' in value)) |
|
return pair(value.toJSON()); |
|
|
|
const entries = []; |
|
const index = as([TYPE, entries], value); |
|
for (const key of keys(value)) { |
|
if (strict || !shouldSkip(typeOf(value[key]))) |
|
entries.push([pair(key), pair(value[key])]); |
|
} |
|
return index; |
|
} |
|
case DATE: |
|
return as([TYPE, value.toISOString()], value); |
|
case REGEXP: { |
|
const {source, flags} = value; |
|
return as([TYPE, {source, flags}], value); |
|
} |
|
case MAP: { |
|
const entries = []; |
|
const index = as([TYPE, entries], value); |
|
for (const [key, entry] of value) { |
|
if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry)))) |
|
entries.push([pair(key), pair(entry)]); |
|
} |
|
return index; |
|
} |
|
case SET: { |
|
const entries = []; |
|
const index = as([TYPE, entries], value); |
|
for (const entry of value) { |
|
if (strict || !shouldSkip(typeOf(entry))) |
|
entries.push(pair(entry)); |
|
} |
|
return index; |
|
} |
|
} |
|
|
|
const {message} = value; |
|
return as([TYPE, {name: type, message}], value); |
|
}; |
|
|
|
return pair; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const serialize = (value, {json, lossy} = {}) => { |
|
const _ = []; |
|
return serializer(!(json || lossy), !!json, new Map, _)(value), _; |
|
}; |
|
|