Use Mapping Configurations
fullName
In this particular case, you can call fullName a computed property because the Source (User)
does not have a fullName property. Hence, it must be computed from something else. To configure the mapping instruction for a property, you can use forMember() with createMap()
import { createMap, forMember, mapFrom } from '@automapper/core';
createMap(
    mapper,
    User,
    UserDto,
    forMember(
        (destination) => destination.fullName,
        mapFrom((source) => source.firstName + ' ' + source.lastName)
    )
);
forMember() accepts two arguments:
- A 
Selectorto select the property you want to configure the instruction for. - A 
MemberMapFnthat carries a specific instruction. 
info
Read more about ForMember
Here, forMember() is saying: For destination.fullName, use source.firstName + ' ' + source.lastName as the result.
birthday
For birthday, let's try using another MappingConfiguration function: typeConverter. Here, we want to convert from Date to string.
createMap(
    mapper,
    Bio,
    BioDto,
    typeConverter(Date, String, (date) => date.toDateString())
);
typeConverter() is saying: If a property on the Source is of type Date and the matching property on the Destination is of type String, use the conversion of date.toDateString()
note
typeConverterwill applyDate -> Stringconversion for ALL pairs ofDate, String, not justbirthday- Instead of 
typeConverter, you can also useforMember()to mapBioDto#birthdayby itself. 
jobTitle and jobSalary
These two properties are a bit different from fullName and birthday. If you notice, you will see that jobTitle and jobSalary are flatten properties of job.title and job.salary.
AutoMapper supports Auto Flattening out of the box with the concept of Naming Conventions. With that in mind, let's provide a NamingConvention for Mapping<Bio, BioDto> by using yet another MappingConfiguration function: namingConventions()
createMap(
    mapper,
    Bio,
    BioDto,
    typeConverter(Date, String, (date) => date.toDateString()),
    namingConventions(new CamelCaseNamingConvention())
);
Let's execute the map operation again, you'll get the complete UserDto 🎉
UserDto {
    bio: BioDto {
        avatarUrl: 'google.com',
        birthday: 'Wed Mar 23 2022',
        jobSalary: 99999,
        jobTitle: 'Developer'
    },
    username: 'ctran',
    lastName: 'Tran',
    firstName: 'Chau',
    fullName: 'Chau Tran'
}
Summary
- Without AutoMapper, mapping logic is repetitive and hard to scale. DTOs and Entities are coupling.
 - With AutoMapper, matching properties are mapped automatically (
firstName,lastName,username,bio, andbio.avatarUrl) - Auto Flattening with Naming Conventions
 - Type Converter allows for using the same conversion for the same pair of types.
 
- user.ts
 - user.dto.ts
 - mapper.ts
 - main.ts
 - user.service.ts
 
export class User {
    @AutoMap()
    firstName: string;
    @AutoMap()
    lastName: string;
    @AutoMap()
    username: string;
    password: string; // <- we purposely left this one out because we don't want to map "password"
    @AutoMap(() => Bio)
    bio: Bio;
}
export class Bio {
    @AutoMap(() => Job)
    job: Job;
    @AutoMap()
    birthday: Date;
    @AutoMap()
    avatarUrl: string;
}
export class Job {
    @AutoMap()
    title: string;
    @AutoMap()
    salary: number;
}
export class UserDto {
    @AutoMap()
    firstName: string;
    @AutoMap()
    lastName: string;
    @AutoMap()
    fullName: string;
    @AutoMap()
    username: string;
    @AutoMap(() => BioDto)
    bio: BioDto;
}
export class BioDto {
    @AutoMap()
    jobTitle: string;
    @AutoMap()
    jobSalary: number;
    @AutoMap()
    birthday: string;
    @AutoMap()
    avatarUrl: string;
}
import { createMapper } from '@automapper/core';
import { classes } from '@automapper/classes';
// Create and export the mapper
export const mapper = createMapper({
    strategyInitializer: classes(),
});
createMap(
    mapper,
    Bio,
    BioDto,
    typeConverter(Date, String, (date) => date.toDateString()),
    namingConventions(new CamelCaseNamingConvention())
);
createMap(
    mapper,
    User,
    UserDto,
    forMember(
        (destination) => destination.fullName,
        mapFrom((source) => source.firstName + ' ' + source.lastName)
    )
);
export class UserService {
    testMapping() {
        const user = new User();
        user.firstName = 'Chau';
        user.lastName = 'Tran';
        user.username = 'ctran';
        user.password = '123456';
        user.bio = new Bio();
        user.bio.avatarUrl = 'google.com';
        user.bio.birthday = new Date();
        user.bio.job = new Job();
        user.bio.job.title = 'Developer';
        user.bio.job.salary = 99999;
        const dto = mapper.map(user, User, UserDto);
    }
}