import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr'; import { Field } from '../../fields/Doc'; import { ClientUtils } from './ClientUtils'; let serializing = 0; export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) { serializing++; cb(err, newValue); serializing--; } export namespace SerializationHelper { export function IsSerializing() { return serializing > 0; } export function Serialize(obj: 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; } } const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise } } = {}; const reverseMap: { [ctor: string]: string } = {}; export function Deserializable(className: 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, afterDeserialize }; reverseMap[ctor.name] = className; } else { throw new Error(`Name ${className} has already been registered as deserializable`); } } return (ctor: { new (...args: any[]): any }) => addToMap(className, 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)) ); }