One of the API response schema is:
{
backdrop_path: string
belongs_to_collection: any
id: number
imdb_id: string
original_language: string
original_title: string
poster_path: string
production_companies: ProductionCompany[]
production_countries: ProductionCountry[]
release_date: Date
spoken_languages: SpokenLanguage[]
video: boolean
vote_average: number
vote_count: number
}
From angular I have to call this API and map to a angular model. But if I make a model similar naming to the schema then it violates the naming convention. So, how can I create a model with proper naming convention which will map to the backend response?
CodePudding user response:
You'll need to explicitly create an instance of your front-end model, and populate it from the JSON object returned by the API.
You can do this by manually writing the code to do it, or by using one of the several available js/ts libraries that are available to do the mapping based on a configuration.
CodePudding user response:
I have a good pattern in a project I made to do this.
Let's say I have this interface (this is my own model):
type ITransaction = {
network_id: string;
transaction_id: string;
commission_amount: number;
order_date?: Date;
type: 'sale' | 'lead' | 'bonus' | 'claim' | 'none';
};
I create a new type called Mapper
type Mapper = {
[K in keyof ITransaction]: ((t: any) => ITransaction[K]) | string;
}
And I create an associated mapper that will map values of the API object to my current model :
const mapper: Mapper = {
transaction_id: 'transaction_id',
commission_amount: (transaction) => Math.round(parseFloat(transaction.commission_amount) * 100),
order_date: 'order_date',
type: ({ type }) => {
if (type === 4 || type === '4') return 'lead';
if (type === 5 || type === '5') return 'sale';
return 'none';
},
};
You can compute the value of your key in your model by two ways:
- By directly calling the key name in the API (you can call nested keys too, like
sale_amount.commission_amount
for instance) - Or by getting directly the complete object and compute it (like with commission or type)
Then, you just have to map it like this
const rawTransaction = await getTransactionFromApi();
const transactionInMyModel: ITransaction = mapNetworkTransaction(rawTransaction, mapper);
Where mapNetworkTransaction is:
const getValue = (obj: Record<string, unknown>, path: string | string[], separator = '.') => {
const properties = Array.isArray(path) ? path : path.split(separator);
return properties.reduce((prev, curr) => prev && prev[curr], obj);
};
const mapNetworkTransaction = (
object: Record<string, string>,
mapper: Mapper,
): ITransaction => {
const txn = { };
// eslint-disable-next-line no-plusplus
for (let i = 0; i < txnFields.length; i ) {
const txnField = txnFields[i];
const accessor = mapper[txnField];
if (accessor) {
if (typeof accessor === 'string') {
txn[txnField] = getValue(object, accessor);
} else if (typeof accessor === 'function') {
txn[txnField] = accessor(object);
}
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return txn;
};
With this pattern, each time you have to map an object API to your model, you'll just have to create a new mapper. And you can do it generic by typing mapper like this:
type Mapper<T> = {
[K in keyof T]: ((t: any) => T[K]) | string;
}
and the mapper function like this:
const mapNetworkTransaction = <T>(
object: Record<string, string>,
mapper: Mapper,
): T