import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr'; // import { Field } from '../../fields/Doc'; let serializing = 0; export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) { serializing++; cb(err, newValue); serializing--; } const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise } } = {}; const reverseMap: { [ctor: string]: string } = {}; export namespace SerializationHelper { export function IsSerializing() { return serializing > 0; } export function Serialize(obj: any /* Field */): any { 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: any): Promise { if (obj === undefined || obj === null) { return undefined; } if (typeof obj !== 'object') { return obj; } if (!obj.__type) { console.warn("No property 'type' found in JSON."); return undefined; } if (!(obj.__type in serializationTypes)) { throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`); } const type = serializationTypes[obj.__type]; const value = await new Promise(res => { deserialize(type.ctor, obj, (err, result) => res(result)); }); type.afterDeserialize?.(value); return value; } } export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { function addToMap(className: string, Ctor: { new (...args: any[]): any }) { const schema = getDefaultModelSchema(Ctor) as any; if (schema.targetClass !== Ctor || constructorArgs) { setDefaultModelSchema(Ctor, { ...schema, factory: (context: any) => 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: any[]): any }) => addToMap(classNameForSerializer, ctor); } export function autoObject(): PropSchema { return custom( s => SerializationHelper.Serialize(s), (json: any, context: any, oldValue: any, cb: (err: any, result: any) => void) => SerializationHelper.Deserialize(json).then(res => cb(null, res)) ); }