hydratable
Version:
Serialize/Deserialize your JSON objects
374 lines (373 loc) • 14.8 kB
JavaScript
"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;