import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr'; import Context from 'serializr/lib/core/Context'; // import { Field } from '../../fields/Doc'; let serializing = 0; export function afterDocDeserialize(cb: (err: unknown, val: unknown) => void, err: unknown, newValue: unknown) { serializing++; cb(err, newValue); serializing--; } const serializationTypes: { [name: string]: { ctor: { new (): unknown }; afterDeserialize?: (obj: unknown) => void | Promise } } = {}; const reverseMap: { [ctor: string]: string } = {}; export namespace SerializationHelper { export function IsSerializing() { return serializing > 0; } export function Serialize(obj: unknown /* Field */): unknown { if (obj === undefined || obj === null) { return null; } if (typeof obj !== 'object') { return obj; } serializing++; if (!(obj.constructor.name in reverseMap)) { serializing--; throw Error(`Error: type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`); } const json = serialize(obj); json.__type = reverseMap[obj.constructor.name]; serializing--; return json; } export async function Deserialize(obj: unknown): Promise { if (obj === undefined || obj === null) { return undefined; } if (typeof obj !== 'object') { return obj; } const objtype = '__type' in obj ? (obj.__type as string) : undefined; if (!objtype) { console.warn(`No property ${objtype} found in JSON.`); return undefined; } if (!(objtype in serializationTypes)) { throw Error(`type '${objtype}' not registered. Make sure you register it using a @Deserializable decorator`); } const type = serializationTypes[objtype]; const value = await new Promise(res => { deserialize(type.ctor, obj, (err, result) => res(result)); }); type.afterDeserialize?.(value); return value; } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: unknown) => void | Promise, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { function addToMap(className: string, Ctor: { new (...args: unknown[]): unknown }) { const schema = getDefaultModelSchema(Ctor); if (schema && (schema.targetClass !== Ctor || constructorArgs)) { setDefaultModelSchema(Ctor, { ...schema, factory: (context: Context) => new Ctor(...(constructorArgs ?? []).map(arg => context.json[arg])) }); } if (!(className in serializationTypes)) { serializationTypes[className] = { ctor: Ctor, afterDeserialize }; reverseMap[Ctor.name] = className; } else { throw new Error(`Name ${className} has already been registered as deserializable`); } } return (ctor: { new (...args: unknown[]): unknown }) => addToMap(classNameForSerializer, ctor); } export function autoObject(): PropSchema { return custom( s => SerializationHelper.Serialize(s), (json: object, context: Context, oldValue: unknown, cb: (err: unknown, result: unknown) => void) => SerializationHelper.Deserialize(json).then(res => cb(null, res)) ); }