Home > Enterprise >  Typescript itterate through typescript object : interface but keep typing
Typescript itterate through typescript object : interface but keep typing

Time:05-23

I have a method which takes an object which is a Partial I wuold like to itterate though that object using Object.entries and then based on the key I wuold like to perform some operations. The interface has keys which are string but values which are string|number|string[]|DateTime. I would like the function I pass it to to know whats being passed in from the key.

The code i currently have is

public partialUpdate = async (id: number, fieldsToUpdate: Partial<IClient>) => {

    type temp = Partial<Record<keyof IClient, (val : string) => void>>;
    const mapping : temp = {
        name: (val) => client.setName(val),
        website: (val) => client.setWebsite(val),
        sic_codes: (val) => client.setSicCodes(val),
        phone_number: (val) => client.setPhoneNumber(val),
    };
    Object.entries(fieldsToUpdate).forEach(([key, value]) => mapping[key](value));
    return this.clientRepository.update(id, client);
};

and the interface is

export interface IClient
{
    id: number,
    creation_date: DateTime,
    name:string,
    website:string,
    phone_number:string,
    industry:string,
    sic_codes:string[],
}

The problem i'm currently having is if it were all string types then no issue but because of sic_codes being a string[] it will cause a type error. Is there any way to have const mapping to know what type val is from the key?

CodePudding user response:

Your original type

Record<keyof IClient, (val: string) => void>

uses the Record<K, V> utility type, which is implemented as a mapped type. Let's write that out explicitly in terms of mapped types:

{[K in keyof IClient]: (val: string) => void}

So, the issue here is that the val type string is not necessarily the value type of IClient at each key K in keyof IClient. And the fix is to replace string with IClient[K], an indexed access type which means "the value type of IClient at key K":

type Test = { [K in keyof IClient]: (val: IClient[K]) => void };
/* type Test = {
  id: (val: number) => void;
  creation_date: (val: Date) => void;
  name: (val: string) => void;
  company_number: (val: string) => void;
  website: (val: string) => void;
  phone_number: (val: string) => void;
  industry: (val: string) => void;
  sic_codes: (val: string[]) => void;
} */

Looks good.


If we plug that in where you had your original type, there are still errors. Object.entries() isn't strongly typed enough to work for what you want in TypeScript. See this SO question for more information. You can give it stronger typings (even though this isn't 100% safe). Even if you do that, your forEach() callback needs to be generic in the particular key type K you're processing. And inside that callback you need to make sure both your value and your mapping[key] are not undefined, if you want to be safe:

Let me make those changes, just to show that it works as you intend:

// Give stronger typings to Object.entries()
type Entry<T extends object> = { [K in keyof T]-?: [K, T[K]] }[keyof T];
function entries<T extends object>(obj: T) {
    return Object.entries(obj) as Entry<T>[];
}

const partialUpdate = async (id: number, fieldsToUpdate: Partial<IClient>) => {
    const client = new Client();    
    const mapping: Partial<{ [K in keyof IClient]: (val: IClient[K]) => void }> = {
        name: (val) => client.setName(val),
        company_number: (val) => client.setCompanyNumber(val),
        industry: (val) => client.setIndustry(val),
        sic_codes: (val) => client.setSicCodes(val),
    };
    const e = entries(fieldsToUpdate).forEach(<K extends keyof IClient>(
        [key, value]: [K, IClient[K] | undefined]) => value && mapping[key]?.(value));
    return null;
};

Hooray, no errors!

Playground link to code

  • Related