UNPKG

hydratable

Version:

Serialize/Deserialize your JSON objects

374 lines (373 loc) 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Hydratable = exports.hy = void 0; /* eslint-disable @typescript-eslint/no-explicit-any */ const buffer_1 = require("buffer"); function hy(type, options = {}) { return function (target, propertyKey) { if (!global.HydratableModelMap) { global.HydratableModelMap = new Map(); } let map = global.HydratableModelMap.get(target.constructor.name); if (!map) { map = new Map(); global.HydratableModelMap.set(target.constructor.name, map); } map.set(propertyKey, { type, options }); }; } exports.hy = hy; class Hydratable { constructor(data) { this.hydrate(data); } toJSON() { const json = {}; const getJSON = (thing) => { if (thing instanceof buffer_1.Buffer) { return thing.toString('base64'); } if (thing instanceof Date) { return thing; } if (thing && thing.toJSON instanceof Function) { return thing.toJSON(); } else if (thing && Array.isArray(thing)) { return thing.map(t => getJSON(t)); } else if (typeof thing === 'object') { const o = {}; for (const key in thing) { o[key] = getJSON(thing[key]); if (o[key] === undefined) { delete o[key]; } } return o; } return thing; }; this.forEachHyProp((key, value) => { var _a, _b, _c; json[((_a = value.options) === null || _a === void 0 ? void 0 : _a.incomingFieldName) || key] = getJSON(this[key]); if (json[((_b = value.options) === null || _b === void 0 ? void 0 : _b.incomingFieldName) || key] === undefined) { delete json[((_c = value.options) === null || _c === void 0 ? void 0 : _c.incomingFieldName) || key]; } }); return json; } diff(other, skip = {}) { const diff = {}; this.forEachHyProp((key, propInfo) => { if (skip[key]) { return; } const field = this[key]; const compareField = other ? other[key] : undefined; if (field && propInfo.type === 'array' && compareField) { const leftToRight = this.diffArrays(field, compareField); const rightToLeft = this.diffArrays(compareField, field, true); if (Object.keys(leftToRight).length || Object.keys(rightToLeft).length) { diff[key] = Object.assign(Object.assign({}, rightToLeft), leftToRight); } } else if (field instanceof Hydratable && compareField instanceof Hydratable) { const subDiff = field.diff(compareField); if (Object.keys(subDiff).length) { diff[key] = subDiff; } } else if (propInfo.type === 'date') { if (!this.areDatesEqual(field, compareField)) { diff[key] = [field, compareField]; } } else if (propInfo.type === 'object') { const leftToRight = this.diffObject(field, compareField); const rightToLeft = this.diffObject(compareField, field, true); if (Object.keys(leftToRight).length || Object.keys(rightToLeft).length) { diff[key] = Object.assign(Object.assign({}, rightToLeft), leftToRight); } } else if (field !== compareField) { diff[key] = [field, compareField]; } }); return diff; } areDatesEqual(a, b) { if (!a && !b) { return true; } else if ((a && !b) || (!a && b)) { return false; } const areEqual = (a === null || a === void 0 ? void 0 : a.getTime()) === (b === null || b === void 0 ? void 0 : b.getTime()); return areEqual; } diffArrays(a, b, inverse) { const diff = {}; for (let i = 0; i < (a || []).length; i++) { const aEl = a ? a[i] : undefined; const bEl = b ? b[i] : undefined; if (aEl instanceof Hydratable && bEl instanceof Hydratable) { const subDiff = inverse ? bEl.diff(aEl) : aEl.diff(bEl); if (Object.keys(subDiff).length) { diff[i] = subDiff; } } else if (this.isArray(aEl) && this.isArray(bEl)) { const leftToRight = this.diffArrays(aEl, bEl); const rightToLeft = this.diffArrays(bEl, aEl, true); if (Object.keys(leftToRight).length || Object.keys(rightToLeft).length) { diff[i] = inverse ? Object.assign(Object.assign({}, leftToRight), rightToLeft) : Object.assign(Object.assign({}, rightToLeft), leftToRight); } } else if (this.isObject(aEl) && this.isObject(bEl)) { const leftToRight = this.diffObject(aEl, bEl); const rightToLeft = this.diffObject(bEl, aEl, true); if (Object.keys(leftToRight).length || Object.keys(rightToLeft).length) { diff[i] = inverse ? Object.assign(Object.assign({}, leftToRight), rightToLeft) : Object.assign(Object.assign({}, rightToLeft), leftToRight); } } else if (aEl !== bEl) { const aElJson = aEl instanceof Hydratable ? aEl.toJSON() : aEl; const bElJson = bEl instanceof Hydratable ? bEl.toJSON() : bEl; diff[i] = inverse ? [bElJson, aElJson] : [aElJson, bElJson]; } } return diff; } diffObject(a, b, inverse) { const diff = {}; for (const key in a) { const field = a[key]; const compareField = b === null || b === void 0 ? void 0 : b[key]; if (field instanceof Hydratable && compareField instanceof Hydratable) { const subDiff = inverse ? compareField.diff(field) : field.diff(compareField); if (Object.keys(subDiff).length) { diff[key] = subDiff; } } if (this.isObject(field) && this.isObject(compareField)) { const leftToRight = this.diffObject(field, compareField); const rightToLeft = this.diffObject(compareField, field, true); if (Object.keys(leftToRight).length || Object.keys(rightToLeft).length) { diff[key] = inverse ? Object.assign(Object.assign({}, leftToRight), rightToLeft) : Object.assign(Object.assign({}, rightToLeft), leftToRight); } } else if (this.isArray(field) && this.isArray(compareField)) { const leftToRight = this.diffArrays(field, compareField); const rightToLeft = this.diffArrays(compareField, field, true); if (Object.keys(leftToRight).length || Object.keys(rightToLeft).length) { diff[key] = inverse ? Object.assign(Object.assign({}, leftToRight), rightToLeft) : Object.assign(Object.assign({}, rightToLeft), leftToRight); } } else if (field !== compareField) { const fieldJson = field instanceof Hydratable ? field.toJSON() : field; const compareFieldJson = compareField instanceof Hydratable ? compareField.toJSON() : compareField; diff[key] = inverse ? [compareFieldJson, fieldJson] : [fieldJson, compareFieldJson]; } } return diff; } hydrate(data) { if (!data || !Object.keys(data).length) { return; } this.forEachHyProp((key, value) => { const incomingKey = value.options.incomingFieldName || key; if (data[incomingKey] === undefined) { return; } this.setField(data, key, value.type, value.options); }); } setField(data, key, type, options) { const incomingKey = options.incomingFieldName || key; switch (type) { case 'number': return this.setNumber(key, data[incomingKey], options); case 'date': return this.setDate(key, data[incomingKey], options); case 'bool': return this.setBool(key, data[incomingKey], options); case 'string': return this.setString(key, data[incomingKey], options); case 'object': return this.setObject(key, data[incomingKey], options); case 'array': return this.setArray(key, data[incomingKey], options); } if (type instanceof Function && data[incomingKey] !== undefined) { if (type === buffer_1.Buffer) { const copy = this.copyBuffer(data[incomingKey], options); if (copy !== undefined) { this.setOnThis(key, copy); } } else { this.setOnThis(key, new type(data[incomingKey])); } } else if (type && 'factory' in type && type.factory instanceof Function && data[incomingKey] !== undefined) { this.setOnThis(key, type.factory(data[incomingKey])); } } setNumber(key, value, options) { if (typeof value === 'string') { value = +value; } if (value === null && !options.allowNull) { value = undefined; } if (!isNaN(value)) { this.setOnThis(key, value); } } setDate(key, value, options) { if (value === null && options.allowNull) { this.setOnThis(key, value); return; } if (!value || (!(value instanceof Date) && !['string', 'number'].includes(typeof value))) { return; } value = new Date(value); if (!isNaN(value.getTime())) { this.setOnThis(key, new Date(value)); } } setBool(key, value, options) { if (typeof value === 'boolean') { this.setOnThis(key, value); return; } if (typeof value === 'string') { value = value.toLowerCase(); if (value === 'true') { this.setOnThis(key, true); } else if (value === 'false') { this.setOnThis(key, false); } return; } if (value === null && options.allowNull) { this.setOnThis(key, null); } } setString(key, value, options) { if (!options.allowNull && value === null) { return; } this.setOnThis(key, value); } setArray(key, value, options) { if (!options.allowNull && value === null) { return; } const type = options.arrayElementType; if (type && type instanceof Function) { this.setOnThis(key, this.copyArray(value).map(el => new type(el))); } else if (typeof type === 'object' && 'factory' in type && type.factory instanceof Function) { this.setOnThis(key, this.copyArray(value).map(el => type.factory(el))); } else { this.setOnThis(key, this.copyArray(value)); } } setObject(key, value, options) { if (!options.allowNull && value === null) { return; } const type = options.dictionaryValueType; if (type && type instanceof Function) { Object.keys(value).forEach(k => { value[k] = new type(value[k]); }); } else if (typeof type === 'object' && 'factory' in type && type.factory instanceof Function) { Object.keys(value).forEach(k => { value[k] = type.factory(value[k]); }); } this.setOnThis(key, this.copyObject(value)); } copyBuffer(value, options) { if (!options.allowNull && value === null) { return; } if (typeof value === 'string') { return buffer_1.Buffer.from(value, 'base64'); } else if (Array.isArray(value) || value instanceof buffer_1.Buffer) { return buffer_1.Buffer.from(value); } } copyArray(value) { const copy = []; if (Array.isArray(value)) { for (const el of value) { if (this.isDate(el)) { copy.push(new Date(el)); } else { copy.push(this.isObject(el) ? this.copyObject(el) : this.isArray(el) ? this.copyArray(el) : el); } } } return copy; } copyObject(obj) { const keys = Object.keys(obj); const copy = {}; for (const key of keys) { const value = obj[key]; if (this.isDate(value)) { copy[key] = new Date(value); } else { copy[key] = (this.isObject(value) && !(value instanceof Hydratable)) ? this.copyObject(value) : value; } } return copy; } forEachHyProp(cb) { var _a; const handledFields = {}; let name = ''; let proto = this['__proto__']; do { name = ((_a = proto === null || proto === void 0 ? void 0 : proto.constructor) === null || _a === void 0 ? void 0 : _a.name) || ''; const map = global.HydratableModelMap.get(name); if (map) { map.forEach((value, key) => { if (handledFields[key]) { return; } handledFields[key] = value; cb(key, value); }); } proto = proto === null || proto === void 0 ? void 0 : proto['__proto__']; } while (name); } isObject(thing) { return typeof thing === 'object' && thing !== null && !Array.isArray(thing); } isArray(thing) { return !!thing && Array.isArray(thing); } isDate(value) { return value instanceof Date || (typeof value === 'string' && /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/.test(value)); } setOnThis(key, val) { this[key] = val; } } exports.Hydratable = Hydratable;