UNPKG

@tsed/json-mapper

Version:
167 lines (166 loc) 5.41 kB
import { ancestorsOf, classOf, getRandomId, hasJsonMethod, isClass, isCollection, isDate, isMomentObject, isMongooseObject, isNil, isObject, isObjectID, isString, nameOf, objectKeys } from "@tsed/core"; /** * Base compiler that turns schema metadata into executable (de)serialization functions. * Subclasses supply `map`, `alterValue`, and `createMapper` implementations to specialize * the pipeline for serialization or deserialization. */ export class JsonMapperCompiler { constructor() { /** * Cached mappers metadata * @protected */ this.cache = new Map(); /** * Cached executable mappers by his id * @protected */ this.mappers = {}; /** * Cached schemas * @protected */ this.schemes = {}; /** * Cached classes by his id * @protected */ this.constructors = {}; /** * Global variables available in the mapper * @protected */ this.globals = { isCollection, isClass, isObject, classOf, nameOf, hasJsonMethod, isMongooseObject, isNil, isDate, objectKeys, isMomentObject }; this.addGlobal("alterIgnore", this.alterIgnore.bind(this)); this.addGlobal("alterValue", this.alterValue.bind(this)); this.addGlobal("execMapper", this.execMapper.bind(this)); this.addGlobal("compileAndMap", this.map.bind(this)); } addTypeMapper(model, fn) { const id = nameOf(model); this.cache.set(model, new Map().set("typeMapper", { id, fn })); this.mappers[id] = fn; return this; } removeTypeMapper(model) { const store = this.cache.get(model); if (store) { const { id } = store.get("typeMapper"); delete this.mappers[id]; this.cache.delete(model); } } addGlobal(key, value) { this.globals[key] = value; return this; } compileMapper(mapper, { id, groupsId, storeGroups }) { const { globals, schemes } = this; const injectGlobals = Object.keys(globals) .map((name) => { return `const ${name} = globals.${name};`; }) .join("\n"); const compileMapper = new Function("globals", "storeGroups", "schemes", "groupsId", "id", `${injectGlobals} storeGroups.set(groupsId, {id, fn: (${mapper})}); return storeGroups.get(groupsId);`); const store = compileMapper(globals, storeGroups, schemes, groupsId, id); this.mappers[id] = store.fn; return store; } createContext(options) { const { cache } = this; return { ...options, cache }; } compile(model, groups, opts = {}) { const token = isString(model) ? model : this.getType(model); const groupsId = this.getGroupsId(groups); let storeGroups = this.cache.get(token) || this.cache.get(nameOf(token)); if (!storeGroups) { storeGroups = new Map(); this.cache.set(token, storeGroups); } if (storeGroups.has("typeMapper")) { return storeGroups.get("typeMapper"); } // generate mapper for the given groups if (!storeGroups.has(groupsId)) { const id = this.getId(token, groupsId); // prevent circular dependencies storeGroups.set(groupsId, { id }); const mapper = opts.mapper ? opts.mapper(id, groups) : this.createMapper(token, id, groups); try { return this.compileMapper(mapper, { id, groupsId, model: token, storeGroups }); } catch (err) { throw new Error(`Fail to compile mapper for ${nameOf(model)}. See the error above: ${err.message}.\n${mapper}`); } } return storeGroups.get(groupsId); } execMapper(id, value, options) { if (isObjectID(value)) { return value.toString(); } return this.mappers[id || nameOf(classOf(value))](value, options); } getType(model) { if (!model) { return Object; } if (isClass(model) && !isCollection(model)) { const type = [Array, Map, Set].find((t) => ancestorsOf(model).includes(t)); if (type) { return type; } } return model; } alterIgnore(id, options) { let result = this.schemes[id]?.$hooks?.alter("ignore", false, [options]); if (result) { return result; } } alterGroups(schema, groups) { if (groups !== false) { return schema.$hooks.alter("groups", false, [groups]); } return false; } getGroupsId(groups) { if (groups === false) { return "default"; } if (groups.length === 0) { return "-"; } return groups.join(","); } getId(model, groupsId) { return `${isString(model) ? model : nameOf(model)}:${getRandomId()}:${groupsId}`; } getSchemaId(id, propertyKey) { return `${id}:${propertyKey}`; } }