Skip to content

Commit

Permalink
refactor(core): extract setMemberReturn function
Browse files Browse the repository at this point in the history
* refactor(core): accept a object as map parameter to adjust optional parameter of function

* fix(core): patch non undefined value when mapMutate

* refactor(core): extract memberMutate function

* refactor(core): extract setMemberReturn function

Co-authored-by: huybn5776 <>
  • Loading branch information
huybn5776 authored Jul 22, 2021
1 parent 416e498 commit b67d5ee
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 40 deletions.
118 changes: 79 additions & 39 deletions packages/core/src/lib/map/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
MemberMapReturn,
} from '@automapper/types';
import { MapFnClassId, TransformationType } from '@automapper/types';
import { isEmpty, set, setMutate } from '../utils';
import { isEmpty, set, setMutate, get } from '../utils';

/**
* Instruction on how to map a particular member on the destination
Expand Down Expand Up @@ -130,18 +130,15 @@ export function mapReturn<
errorHandler: ErrorHandler,
isMapArray = false
): TDestination {
return map(
return map({
sourceObj,
mapping,
options,
mapper,
errorHandler,
(destinationMemberPath: string[], destination: TDestination) =>
(value: unknown) => {
destination = set(destination, destinationMemberPath, value);
},
isMapArray
);
setMemberFn: setMemberReturnFn,
isMapArray,
});
}

/**
Expand All @@ -164,19 +161,30 @@ export function mapMutate<
errorHandler: ErrorHandler,
destinationObj: TDestination
): void {
map(
map({
sourceObj,
mapping,
options,
mapper,
errorHandler,
(destinationMember: string[]) => (value: unknown) => {
if (value === undefined) {
return;
}
setMutate(destinationObj, destinationMember, value);
}
);
setMemberFn: setMemberMutateFn(destinationObj),
getMemberFn: getMemberMutateFn(destinationObj)
});
}

interface MapParameter<TSource extends Dictionary<TSource> = any,
TDestination extends Dictionary<TDestination> = any> {
sourceObj: TSource;
mapping: Mapping<TSource, TDestination>;
options: MapOptions<TSource, TDestination>;
mapper: Mapper;
errorHandler: ErrorHandler;
setMemberFn: (
destinationMemberPath: string[],
destination?: TDestination,
) => (value: unknown) => void;
getMemberFn?: (destinationMemberPath: string[] | undefined) => Record<string, unknown>;
isMapArray?: boolean;
}

/**
Expand All @@ -187,23 +195,20 @@ export function mapMutate<
* @param {Mapper} mapper - the mapper instance
* @param {ErrorHandler} errorHandler - the error handler
* @param {Function} setMemberFn
* @param {Function} getMemberFn
* @param {boolean} [isMapArray = false] - whether the map operation is in Array mode
*/
function map<
TSource extends Dictionary<TSource> = any,
TDestination extends Dictionary<TDestination> = any
>(
sourceObj: TSource,
mapping: Mapping<TSource, TDestination>,
options: MapOptions<TSource, TDestination>,
mapper: Mapper,
errorHandler: ErrorHandler,
setMemberFn: (
destinationMemberPath: string[],
destination?: TDestination
) => (value: unknown) => void,
isMapArray = false
) {
function map<TSource extends Dictionary<TSource> = any,
TDestination extends Dictionary<TDestination> = any>({
sourceObj,
mapping,
options,
mapper,
errorHandler,
setMemberFn,
getMemberFn,
isMapArray = false,
}: MapParameter) {
// destructure the mapping
let [[, destination], propsToMap, [mappingBeforeAction, mappingAfterAction]] =
mapping;
Expand Down Expand Up @@ -330,18 +335,32 @@ Original error: ${originalError}`;
nestedSourceMemberKey,
nestedDestinationMemberKey
);

// nested mutate
const destinationMemberValue = getMemberFn?.(destinationMemberPath);
if (destinationMemberValue !== undefined) {
map({
sourceObj: mapInitializedValue,
mapping: nestedMapping!,
options: { extraArguments },
mapper,
errorHandler,
setMemberFn: setMemberMutateFn(destinationMemberValue),
getMemberFn: getMemberMutateFn(destinationMemberValue)
});
continue;
}

// for nested model, we do not care about mutate or return. we will always need to return
setMember(() =>
map(
mapInitializedValue,
nestedMapping!,
{ extraArguments },
map({
sourceObj: mapInitializedValue,
mapping: nestedMapping!,
options: { extraArguments },
mapper,
errorHandler,
(memberPath, nestedDestination) => (value) => {
nestedDestination = set(nestedDestination, memberPath, value);
}
)
setMemberFn: setMemberReturnFn,
}),
);
continue;
}
Expand Down Expand Up @@ -433,3 +452,24 @@ export function mapArray<

return destinationArray;
}

function setMemberMutateFn(destinationObj: Record<string, unknown>) {
return (destinationMember: string[]) => (value) => {
if (value !== undefined) {
setMutate(destinationObj, destinationMember, value);
}
};
}

function getMemberMutateFn(destinationObj: Record<string, unknown>) {
return (memberPath: string[] | undefined) => get(destinationObj, memberPath) as Record<string, unknown>;
}

function setMemberReturnFn<TDestination extends Dictionary<TDestination> = any>(
destinationMemberPath: string[],
destination: TDestination
) {
return (value: unknown) => {
destination = set(destination, destinationMemberPath, value);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export const avatarProfile: MappingProfile = (mapper) => {
.forMember((d) => d.willBeIgnored, ignore())
.forMember((d) => d.shouldBeSubstituted, nullSubstitution('sub'));

mapper
.createMap(AvatarVm, Avatar)
.forMember((d) => d.shouldIgnore, ignore());

mapper
.createMap(Avatar, PascalAvatarVm)
.forMember(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { convertUsing, mapWith } from '@automapper/core';
import { convertUsing, mapWith, mapFrom } from '@automapper/core';
import type { MappingProfile } from '@automapper/types';
import { dateToStringConverter } from '../converters/date-to-string.converter';
import { Avatar, AvatarVm } from '../models/avatar';
Expand Down Expand Up @@ -30,6 +30,13 @@ export const userProfileProfile: MappingProfile = (mapper) => {
convertUsing(dateToStringConverter, (s) => s.birthday)
);

mapper
.createMap(UserProfileVm, UserProfile)
.forMember(
(d) => d.birthday,
mapFrom(s => s.birthday && new Date(s.birthday)),
);

mapper
.createMap(UserProfile, PascalUserProfileVm)
.forMember(
Expand Down
30 changes: 30 additions & 0 deletions packages/integration-test/src/lib/with-classes/map-mutate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
userProfile,
} from './fixtures/profiles/user.profile';
import { getPascalUser, getUser } from './utils/get-user';
import { UserProfileVm } from './fixtures/models/user-profile';
import { AvatarVm } from './fixtures/models/avatar';

describe('Map Mutate', () => {
const [mapper] = setupClasses('mapMutate');
Expand Down Expand Up @@ -107,6 +109,34 @@ describe('Map Mutate', () => {
expect(user.job).toEqual(originalUser.job);
});

it('should not map undefined properly of nested field', () => {
mapper
.addProfile(addressProfile)
.addProfile(avatarProfile)
.addProfile(userProfileProfile)
.addProfile(userProfile);

const originalUser = getUser();
const user = { ...getUser() };

const vm = new UserVm();
vm.profile = {
bio: 'Outgoing-ish',
avatar: { url: 'uniform-resource-locator.com' } as AvatarVm,
} as UserProfileVm;

mapper.map(vm, User, UserVm, user);
expect(user.profile.bio).toEqual(vm.profile.bio);
expect(user.profile.avatar.url).toEqual(vm.profile.avatar.url);

expect(user.profile.birthday).toEqual(originalUser.profile.birthday);
expect(user.profile.avatar.source).toEqual(originalUser.profile.avatar.source);
expect(user.profile.avatar.shouldIgnore).toEqual(originalUser.profile.avatar.shouldIgnore);
expect(user.profile.avatar.shouldBeSubstituted).toEqual(originalUser.profile.avatar.shouldBeSubstituted);
expect(user.profile.avatar.forCondition).toEqual(originalUser.profile.avatar.forCondition);
expect(user.profile.addresses).toEqual(originalUser.profile.addresses);
});

it('should map correctly with condition, preCondition, and nullSubstitution', () => {
mapper
.addProfile(addressProfile)
Expand Down

0 comments on commit b67d5ee

Please sign in to comment.