Home > Software engineering >  What is the right way of mapping API model to angular(frontend) model?
What is the right way of mapping API model to angular(frontend) model?

Time:12-28

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
  • Related