UNPKG

ravendb

Version:
350 lines 13.2 kB
import { throwError } from "../Exceptions/index.js"; import { TypeUtil } from "../Utility/TypeUtil.js"; import { getLogger } from "../Utility/LogUtil.js"; import { ObjectUtil } from "../Utility/ObjectUtil.js"; import { CONSTANTS } from "../Constants.js"; const log = getLogger({ module: "ObjectMapper" }); export class TypesAwareObjectMapper { _throwMappingErrors = false; _conventions; constructor(opts) { if (opts) { if (!opts.documentConventions) { throwError("InvalidArgumentException", "Document conventions cannot be empty."); } this._conventions = opts.documentConventions; } } get throwMappingErrors() { return this._throwMappingErrors; } set throwMappingErrors(value) { this._throwMappingErrors = value; } fromObjectLiteral(rawResult, typeInfo, knownTypes) { rawResult = ObjectUtil.deepLiteralClone(rawResult); const typeName = typeInfo ? typeInfo.typeName : null; const nestedTypes = typeInfo ? typeInfo.nestedTypes : null; const types = knownTypes || this._conventions.knownEntityTypesByName; const ctorOrTypeDescriptor = this._getKnownType(typeName, types); const result = this._instantiateObject(typeName, rawResult, ctorOrTypeDescriptor); this._applyNestedTypes(result, nestedTypes, types); return result; } _applyNestedTypes(obj, nestedTypes, knownTypes) { if (!nestedTypes || Object.keys(nestedTypes).length === 0) { return obj; } const nestedTypesKeys = Object.keys(nestedTypes); nestedTypesKeys.sort(); for (const propertyPath of nestedTypesKeys) { const typeName = nestedTypes[propertyPath]; const objPathSegments = propertyPath .replace(/\[/g, "![") .replace(/\$MAP/g, "!$MAP") .replace(/\$SET/g, "!$SET") .split(/[!.]/g); const fieldContext = this._getFieldContext(obj, objPathSegments); const fieldContexts = Array.isArray(fieldContext) ? fieldContext : [fieldContext]; for (const [i, c] of fieldContexts.entries()) { this._applyTypeToNestedProperty(typeName, c, knownTypes); } } return obj; } toObjectLiteral(obj, typeInfoCallback, knownTypes) { const types = (knownTypes || this._conventions.knownEntityTypesByName); let nestedTypes; const result = this._makeObjectLiteral(obj, null, (nestedType) => { nestedTypes = Object.assign(nestedTypes || {}, nestedType); }, Array.from(types.values())); let typeName; if (TypeUtil.isClass(obj)) { typeName = obj.constructor.name; } else { const typeDescriptor = TypeUtil.findType(obj, Array.from(types.values())); typeName = typeDescriptor ? typeDescriptor.name : null; } const typeInfo = {}; typeInfo.typeName = typeName || null; typeInfo.nestedTypes = nestedTypes || {}; if (typeInfoCallback) { typeInfoCallback(typeInfo); } return result; } _getFieldContext(parent, objPath) { if (!parent) { return null; } // eslint-disable-next-line prefer-const let [field, ...fieldsPathTail] = objPath; const isFieldArray = field.endsWith("[]"); if (isFieldArray) { field = field.replace(/\[\]$/g, ""); } const isFieldSet = field.endsWith("$SET"); if (isFieldSet) { field = field.replace(/\$SET$/g, ""); } const isFieldMap = field.endsWith("$MAP"); if (isFieldMap) { field = field.replace(/\$MAP$/g, ""); } const fieldNameConvention = this._conventions.serverToLocalFieldNameConverter; if (fieldNameConvention) { field = fieldNameConvention(field); } let fieldVal = parent[field]; // eslint-disable-next-line no-prototype-builtins if (!parent.hasOwnProperty(field)) { if (isFieldArray || isFieldSet || isFieldMap) { fieldVal = parent; } else { return null; } } if (isFieldArray) { return this._getFieldContextsForArrayElements(fieldVal, fieldsPathTail); } if (isFieldSet) { return this._getFieldContextsForSetElements(fieldVal, fieldsPathTail); } if (isFieldMap) { return this._getFieldContextsForMapEntries(fieldVal, fieldsPathTail); } if (fieldsPathTail.length) { return this._getFieldContext(parent[field], fieldsPathTail); } return { parent, field, getValue() { return parent[field]; }, setValue(val) { parent[field] = val; } }; } _getFieldContextsForMapEntries(mapFieldVal, fieldsPathTail) { const result = Array.from(mapFieldVal.entries()).map(([key, val]) => { if (!fieldsPathTail.length) { return { parent: mapFieldVal, field: key, getValue: () => val, setValue: (newVal) => { mapFieldVal.set(key, newVal); } }; } else { return this._getFieldContext(val, fieldsPathTail); } }); return this._flattenFieldContexts(result); } _getFieldContextsForSetElements(setFieldVal, fieldsPathTail) { const result = Array.from(setFieldVal).map(x => { if (!fieldsPathTail.length) { return { parent: setFieldVal, field: x, getValue: () => x, setValue: (val) => { setFieldVal.delete(x); setFieldVal.add(val); } }; } else { return this._getFieldContext(x, fieldsPathTail); } }); return this._flattenFieldContexts(result); } _getFieldContextsForArrayElements(fieldVal, fieldsPathTail) { const result = fieldVal.map((x, i) => { if (x === null) { return null; } if (!fieldsPathTail.length) { return { parent: fieldVal, field: i.toString(), getValue() { return fieldVal[i]; }, setValue(val) { fieldVal[i] = val; } }; } else { return this._getFieldContext(x, fieldsPathTail); } }); return this._flattenFieldContexts(result); } _flattenFieldContexts(arr) { return arr.reduce((result, next) => { if (Array.isArray(next)) { return result.concat(next); } result.push(next); return result; }, []); } _applyTypeToNestedProperty(fieldTypeName, fieldContext, knownTypes) { let parent; let field; if (fieldContext) { ({ parent, field } = fieldContext); } if (typeof parent === "undefined") { return; } const fieldVal = fieldContext.getValue(); if (typeof fieldVal === "undefined") { return; } if (fieldVal === null) { fieldContext.setValue(null); return; } if (fieldTypeName === "date") { fieldContext.setValue(this._conventions.dateUtil.parse(fieldVal)); return; } if (fieldTypeName === "Set") { fieldContext.setValue(new Set(fieldVal)); return; } if (fieldTypeName === "Map") { const map = new Map(fieldVal); fieldContext.setValue(map); return; } if (Array.isArray(fieldVal)) { for (const [i, item] of fieldVal.entries()) { this._applyTypeToNestedProperty(fieldTypeName, { field: i.toString(), parent: fieldVal, getValue: () => fieldVal[i], setValue: (val) => fieldVal[i] = val }, knownTypes); } return; } const ctorOrTypeDescriptor = this._getKnownType(fieldTypeName, knownTypes); const instance = this._instantiateObject(fieldTypeName, fieldVal, ctorOrTypeDescriptor); fieldContext.setValue(instance); } _instantiateObject(typeName, rawValue, ctorOrTypeDescriptor) { let instance = null; if (!ctorOrTypeDescriptor) { instance = Object.assign({}, rawValue); } else if (TypeUtil.isClass(ctorOrTypeDescriptor)) { instance = this.createEmptyObject(ctorOrTypeDescriptor, rawValue); } else if (TypeUtil.isObjectLiteralTypeDescriptor(ctorOrTypeDescriptor)) { instance = ctorOrTypeDescriptor.construct(rawValue); } else { throwError("InvalidArgumentException", `Invalid type descriptor for type ${typeName}: ${ctorOrTypeDescriptor}`); } return instance; } _getKnownType(typeName, knownTypes) { if (!typeName) { return null; } const ctorOrTypeDescriptor = knownTypes.get(typeName); if (!ctorOrTypeDescriptor) { if (this._throwMappingErrors) { throwError("MappingError", `Could not find type descriptor '${typeName}'.`); } else { log.warn(`Could not find type descriptor '${typeName}'.`); } } return ctorOrTypeDescriptor; } createEmptyObject(ctor, rawValue) { if (!ctor) { throwError("InvalidArgumentException", "ctor argument must not be null or undefined."); } return Object.assign(new ctor(), rawValue); } _makeObjectLiteral(obj, objPathPrefix, typeInfoCallback, knownTypes, skipTypes = false) { if (TypeUtil.isDate(obj)) { if (!skipTypes) { typeInfoCallback({ [objPathPrefix]: "date" }); } return this._conventions.dateUtil.stringify(obj); } if (TypeUtil.isSet(obj)) { if (!skipTypes) { typeInfoCallback({ [objPathPrefix]: "Set" }); } const newObjPathPrefix = `${objPathPrefix}$SET`; return Array.from(obj) .map(x => this._makeObjectLiteral(x, newObjPathPrefix, typeInfoCallback, knownTypes)); } if (TypeUtil.isMap(obj)) { if (!skipTypes) { typeInfoCallback({ [objPathPrefix]: "Map" }); } const valuePathPrefix = `${objPathPrefix}$MAP`; const map = obj; return Array.from(map.entries()).reduce((result, [name, value]) => { return [ ...result, [ this._makeObjectLiteral(name, valuePathPrefix + "KEY", typeInfoCallback, knownTypes), this._makeObjectLiteral(value, valuePathPrefix, typeInfoCallback, knownTypes) ] ]; }, []); } if (Array.isArray(obj)) { return obj.map((x, index) => this._makeObjectLiteral(x, `${objPathPrefix}.${index}`, typeInfoCallback, knownTypes)); } if (TypeUtil.isObject(obj)) { if (objPathPrefix) { // if it's non-root object const matchedType = TypeUtil.findType(obj, knownTypes); if (!skipTypes && matchedType && matchedType.name !== "Function") { typeInfoCallback({ [objPathPrefix]: matchedType.name }); } } return Object.keys(obj) .reduce((result, key) => { let nestedTypeInfoKey = key; if (this._conventions.localToServerFieldNameConverter) { nestedTypeInfoKey = this._conventions.localToServerFieldNameConverter(key); } let innerSkipTypes = skipTypes; if (!skipTypes) { innerSkipTypes = key === CONSTANTS.Documents.Metadata.KEY; } const fullPath = objPathPrefix ? `${objPathPrefix}.${nestedTypeInfoKey}` : nestedTypeInfoKey; result[key] = this._makeObjectLiteral(obj[key], fullPath, typeInfoCallback, knownTypes, innerSkipTypes); return result; }, {}); } return obj; } } //# sourceMappingURL=ObjectMapper.js.map