From 46e920db51f7c8042926064696dc894a502a509f Mon Sep 17 00:00:00 2001 From: Chau Tran Date: Thu, 7 Jan 2021 15:30:43 -0600 Subject: [PATCH] feat(core): add ability to pass in destinationObj and mutate on map instead of return --- packages/core/src/lib/create-mapper.ts | 48 ++++++++++-- packages/core/src/lib/map.ts | 101 +++++++++++++++++++++---- packages/types/src/core.ts | 44 +++++++++++ 3 files changed, 171 insertions(+), 22 deletions(-) diff --git a/packages/core/src/lib/create-mapper.ts b/packages/core/src/lib/create-mapper.ts index 3a9c1c88a..55c85b3b4 100644 --- a/packages/core/src/lib/create-mapper.ts +++ b/packages/core/src/lib/create-mapper.ts @@ -19,7 +19,7 @@ import { MappingPropertiesClassId, TransformationType, } from '@automapper/types'; -import { map, mapArray } from './map'; +import { mapArray, mapMutate, mapReturn } from './map'; import { getMemberPath } from './utils'; /** @@ -58,7 +58,7 @@ export function createMapper({ profile(this); return this; }, - map(sourceObj, destination, source, options?) { + map(sourceObj, destination, source, destinationObjOrOptions?, options?) { const { preMap } = plugin; // run preMap if available @@ -67,17 +67,51 @@ export function createMapper({ // get mapping between Source and Destination const mapping = this.getMapping(source, destination); - // map - return map( + // check mutate or return + + // if destinationObjOrOptions has beforeMap or afterMap + // or destinationObjOrOptions is null/undefined => this is a mapReturn + // TODO(chau): this might fail if destinationObj has a beforeMap/afterMap property on the consumer side. + if ( + (destinationObjOrOptions && + ('beforeMap' in destinationObjOrOptions || + 'afterMap' in destinationObjOrOptions)) || + destinationObjOrOptions == null + ) { + return mapReturn( + sourceInstance ?? sourceObj, + mapping, + destinationObjOrOptions, + this, + errorHandler + ); + } + + mapMutate( sourceInstance ?? sourceObj, mapping, options, this, - errorHandler + errorHandler, + destinationObjOrOptions ); }, - mapAsync(sourceObj, destination, source, options?) { - return Promise.resolve(this.map(sourceObj, destination, source, options)); + mapAsync( + sourceObj, + destination, + source, + destinationObjOrOptions, + options? + ) { + return Promise.resolve( + this.map( + sourceObj, + destination, + source, + destinationObjOrOptions, + options + ) + ); }, mapArray(sourceArr, destination, source, options) { return mapArray( diff --git a/packages/core/src/lib/map.ts b/packages/core/src/lib/map.ts index 73a0da068..0881e00a8 100644 --- a/packages/core/src/lib/map.ts +++ b/packages/core/src/lib/map.ts @@ -16,7 +16,7 @@ import type { NullSubstitutionFunction, } from '@automapper/types'; import { MapFnClassId, TransformationType } from '@automapper/types'; -import { isEmpty, set } from './utils'; +import { isEmpty, set, setMutate } from './utils'; /** * Instruction on how to map a particular member on the destination @@ -112,7 +112,7 @@ ${unmappedKeys.join(',\n')} * @param {ErrorHandler} errorHandler - the error handler * @param {boolean} [isMapArray = false] - whether the map operation is in Array mode */ -export function map< +export function mapReturn< TSource extends Dictionary = unknown, TDestination extends Dictionary = unknown >( @@ -122,6 +122,74 @@ export function map< mapper: Mapper, errorHandler: ErrorHandler, isMapArray = false +): TDestination { + const setMemberReturn = ( + destinationMemberPath: string, + destination: TDestination + ) => (value: unknown) => { + destination = set(destination, destinationMemberPath, value); + }; + return map( + sourceObj, + mapping, + options, + mapper, + errorHandler, + setMemberReturn, + isMapArray + ); +} + +/** + * + * @param {TSource} sourceObj - the source object + * @param {Mapping} mapping - the Mapping object of source <> destination + * @param {MapOptions} options - options used for this particular map operation + * @param {Mapper} mapper - the mapper instance + * @param {ErrorHandler} errorHandler - the error handler + * @param {TDestination} destinationObj - the destination obj to be mutated + */ +export function mapMutate< + TSource extends Dictionary = unknown, + TDestination extends Dictionary = unknown +>( + sourceObj: TSource, + mapping: Mapping, + options: MapOptions, + mapper: Mapper, + errorHandler: ErrorHandler, + destinationObj: TDestination +): void { + const setMemberMutate = (destinationMember: string) => (value: unknown) => { + setMutate(destinationObj, destinationMember, value); + }; + map(sourceObj, mapping, options, mapper, errorHandler, setMemberMutate); +} + +/** + * + * @param {TSource} sourceObj - the source object + * @param {Mapping} mapping - the Mapping object of source <> destination + * @param {MapOptions} options - options used for this particular map operation + * @param {Mapper} mapper - the mapper instance + * @param {ErrorHandler} errorHandler - the error handler + * @param {Function} setMemberFn + * @param {boolean} [isMapArray = false] - whether the map operation is in Array mode + */ +function map< + TSource extends Dictionary = unknown, + TDestination extends Dictionary = unknown +>( + sourceObj: TSource, + mapping: Mapping, + options: MapOptions, + mapper: Mapper, + errorHandler: ErrorHandler, + setMemberFn: ( + destinationMemberPath: string, + destination?: TDestination + ) => (value: unknown) => void, + isMapArray = false ) { // destructure the mapping let [ @@ -164,8 +232,7 @@ export function map< ] = propsToMap[i]; // Setup a shortcut function to set destinationMemberPath on destination with value as argument - const setMember = (value: unknown) => - set(destination, destinationMemberPath, value); + const setMember = setMemberFn(destinationMemberPath, destination); // This destination key is being configured. Push to configuredKeys array configuredKeys.push(destinationMemberPath); @@ -175,7 +242,7 @@ export function map< transformationPreCondPredicate && !transformationPreCondPredicate(sourceObj) ) { - destination = setMember(preCondDefaultValue); + setMember(preCondDefaultValue); continue; } @@ -190,13 +257,13 @@ export function map< // if null/undefined if (mapInitializedValue == null) { - destination = setMember(mapInitializedValue); + setMember(mapInitializedValue); continue; } // if isDate if (mapInitializedValue instanceof Date) { - destination = setMember(new Date(mapInitializedValue)); + setMember(new Date(mapInitializedValue)); continue; } @@ -205,17 +272,17 @@ export function map< const [first] = mapInitializedValue; // if first item is a primitive if (typeof first !== 'object') { - destination = setMember(mapInitializedValue.slice()); + setMember(mapInitializedValue.slice()); continue; } // if first is empty if (isEmpty(first)) { - destination = setMember([]); + setMember([]); continue; } - destination = setMember( + setMember( mapArray( mapInitializedValue, nestedDestinationMemberKey, @@ -234,24 +301,28 @@ export function map< nestedSourceMemberKey, nestedDestinationMemberKey ); - destination = setMember( + // for nested model, we do not care about mutate or return. we will always need to return + setMember( map( mapInitializedValue, nestedMapping, undefined, mapper, - errorHandler + errorHandler, + (memberPath, nestedDestination) => (value) => { + nestedDestination = set(nestedDestination, memberPath, value); + } ) ); continue; } // if is primitive - destination = setMember(mapInitializedValue); + setMember(mapInitializedValue); continue; } - destination = setMember( + setMember( mapMember( transformationMapFn, sourceObj, @@ -308,7 +379,7 @@ export function mapArray< for (let i = 0, len = sourceArray.length; i < len; i++) { const mapping = mapper.getMapping(source, destination); destinationArray.push( - map( + mapReturn( sourceArray[i], mapping as Mapping, undefined, diff --git a/packages/types/src/core.ts b/packages/types/src/core.ts index f873ef489..658ee3dc5 100644 --- a/packages/types/src/core.ts +++ b/packages/types/src/core.ts @@ -269,6 +269,28 @@ export interface Mapper { options?: MapOptions ): TDestination; + map< + TSource extends Dictionary = unknown, + TDestination extends Dictionary = unknown + >( + sourceObj: TSource, + destination: new (...args: unknown[]) => TDestination, + source: new (...args: unknown[]) => TSource, + destinationObj: TDestination, + options?: MapOptions + ): void; + + map< + TSource extends Dictionary, + TDestination extends Dictionary + >( + sourceObj: TSource, + destination: string, + source: string, + destinationObj: TDestination, + options?: MapOptions + ): void; + mapAsync< TSource extends Dictionary = unknown, TDestination extends Dictionary = unknown @@ -289,6 +311,28 @@ export interface Mapper { options?: MapOptions ): Promise; + mapAsync< + TSource extends Dictionary = unknown, + TDestination extends Dictionary = unknown + >( + sourceObj: TSource, + destination: new (...args: unknown[]) => TDestination, + source: new (...args: unknown[]) => TSource, + destinationObj: TDestination, + options?: MapOptions + ): Promise; + + mapAsync< + TSource extends Dictionary, + TDestination extends Dictionary + >( + sourceObj: TSource, + destination: string, + source: string, + destinationObj: TDestination, + options?: MapOptions + ): Promise; + mapArray< TSource extends Dictionary = unknown, TDestination extends Dictionary = unknown