Notes
Profile Naming
What you chose to name the profile doesn't matter to the mapper. In this documentation, we used the pattern ${sourceKey}To${destinationKey}
but this is purely for your organization and is not currently used by Kitbag Mapper in any way.
Simplifying Profiles
You might start to notice that authoring profiles can sometimes feel verbose. Especially if you're mapping models with a lot of properties.
export const userApiResponseToUser = {
sourceKey: 'UserApiResponse',
destinationKey: 'User',
map: function (source: UserApiResponse): User {
return {
id: source.id.toString(),
createdDate: new Date(source.created_date),
language: source.language,
timezone: source.timezone,
emailAddress: source.email_address,
isActive: source.is_active,
marketingEmails: source.marketing_emails,
firstName: source.first_name,
lastName: source.last_name,
dateOfBirth: source.date_of_birth,
phoneNumber: source.phone_number,
}
},
} as const satisfies Profile
import { ObjectId } from 'mongodb'
export type UserApiResponse = {
id: ObjectId,
email_address: string,
is_active: boolean,
created_date: string,
language: string,
timezone: string,
marketing_emails: boolean,
first_name: string,
last_name: string,
date_of_birth: Date,
phone_number?: string,
}
export type User = {
id: string,
emailAddress: string,
isActive: boolean,
createdDate: Date,
language: string,
timezone: string,
marketingEmails: boolean,
firstName: string,
lastName: string,
dateOfBirth: Date,
phoneNumber?: string,
}
There are several ways you can simplify the profile for mapping between these types without sacrificing type safety.
Spread Operator
there are a couple properties on these models that are the same property name and type, we could use a spread operator to include these
export const userApiResponseToUser = {
sourceKey: 'UserApiResponse',
destinationKey: 'User',
map: function (source: UserApiResponse): User {
return {
...source,
id: source.id.toString(),
createdDate: new Date(source.created_date),
language: source.language,
timezone: source.timezone,
emailAddress: source.email_address,
isActive: source.is_active,
marketingEmails: source.marketing_emails,
firstName: source.first_name,
lastName: source.last_name,
dateOfBirth: source.date_of_birth,
phoneNumber: source.phone_number,
}
},
} as const satisfies Profile
While more concise, this solution also blindly copies properties including the snake cased properties or properties you don't care about to the new object which might not be desired.
Converting Casing
Thanks to great libraries like StringTs, we can convert snake cased properties into camel cased properties automatically both at runtime and type levels. Leaving your profile only responsible for declaring the business logic that's unique.
import { camelKeys } from 'string-ts'
export const userApiResponseToUser = {
sourceKey: 'UserApiResponse',
destinationKey: 'User',
map: function (source: UserApiResponse): User {
return {
...camelKeys(source),
id: source.id.toString(),
createdDate: new Date(source.created_date),
language: source.language,
timezone: source.timezone,
emailAddress: source.email_address,
isActive: source.is_active,
marketingEmails: source.marketing_emails,
firstName: source.first_name,
lastName: source.last_name,
dateOfBirth: source.date_of_birth,
phoneNumber: source.phone_number,
}
},
} as const satisfies Profile
Bad Ideas
Avoid casting in your map function. This breaks type safety and will swallow potential errors when underlying models change.
import { camelCase } from 'string-ts'
export const userApiResponseToUser = {
sourceKey: 'UserApiResponse',
destinationKey: 'User',
map: function (source: UserApiResponse): User {
return someMagicUtil(source) as User
},
} as const satisfies Profile