简体   繁体   中英

Typescript Generic Object Map

Need some help finishing off a generic object.map function, my current implementation is both extremely slow on the compiler to the point where it breaks (but works) and currently i can't think of a good way to infer the ReturnType

type DeepKeyOf<T extends object> = {
    [K in keyof T]: T[K] extends object ? DeepKeyOf<T[K]> | { key: K; value: T[K] } : { key: K; value: T[K] }
}[keyof T];

function objectMap<T extends object, B>(obj: T, mapper: (value: DeepKeyOf<T>) => any): T {
    return Object.keys(obj).reduce((newObj, key) => {
        return { ...newObj, [key]: mapper(obj[key]) };
    }, {}) as any;
}

This will give back a discriminated union of {key, value} object literal pairs such that you can check the key with key === "foo" and get the typeguarded value of that key back which is what i want.

2 problems.

  1. DeepKeyOf often stops compiler working (unperformant) even on very small interfaces
  2. Can't infer ReturnType (related to "T" on very end) and 'any' on mapper function

If someone says they can solve the problem with higher-kinded types give me the pseudo code perferably with scala syntax for HKT"s and i can write out a implementation that should finish it off.

EDIT:

type DeepKeyOf<T extends object> = {
    [K in keyof T]: T[K] extends object ? DeepKeyOf<T[K]> | { key: K; value: T[K] } : { key: K; value: T[K] }
}[keyof T];
interface IPerson {
    name: "susan";
    children: {
        billy: string;
        sally: Date;
    };
}

will give back {key: "name", value: "susan"} | {key: "children", value: {billy: string, sally: Date} | {key: "billy", value: string} | {key: "sally", value: Date} {key: "name", value: "susan"} | {key: "children", value: {billy: string, sally: Date} | {key: "billy", value: string} | {key: "sally", value: Date}

EDIT: This is how far iv'e gotten which is close, but it only works when all keys are handled individually if you remove the "if key == "dob"" the ReturnType comes out as not what i want because Dob is a union of the other types.

// also i went with just making it shallow object.map to make it easier on the compiler and myself.

export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
    ? I
    : never;

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type ShallowKeyOf<T extends object> = { [K in keyof T]: { key: K; value: T[K] } }[keyof T];

interface IPerson {
    name: "susan";
    children: {
        billy: number;
        sally: Date;
    };
    dob: Date;
}

type GenerateRecord<Tuple extends [any, any]> = Tuple extends infer SELF
    ? SELF extends [infer K, infer V] ? (K extends string ? Record<K, V> : never) : never
    : never;

type ChangeValue<T extends object, Tuple extends [keyof T, any]> = Omit<T, Tuple[0]> extends infer SELF
    ? SELF & UnionToIntersection<GenerateRecord<Tuple>>
    : never;


function objectMap<T extends object, TupleReturn extends [keyof T & string, any]>(
    obj: T,
    mapper: (value: ShallowKeyOf<T>) => TupleReturn
): ChangeValue<T, TupleReturn> {
    return Object.keys(obj).reduce((newObj, key) => {
        return { ...newObj, [key]: mapper((obj as any)[key]) };
    }, {}) as any;
}

const test = objectMap(("" as any) as IPerson, ({ key, value }) => {
    if (key === "name") {
        return [key, new Date()];
    }
    if (key === "dob") {
        return [key, "NOW STRING"]
    }
    return [key, new Date()];
});

Here is an approach I take by breaking apart the object-ey keys and non-object-ey keys. I think its a much wider type space to extend object , and it typically gives me a hard time because it includes things like Dates.

type NonObject = Date | RegExp | string | number;

type ObjectValuedKeys<T> = {
  [K in keyof T]: T[K] extends NonObject ?
  never :
  { o: K }
}[keyof T]['o'];

type NonObjectValuedKeys<T> = {
  [K in keyof T]: T[K] extends NonObject ?
  { o: K } :
  never
}[keyof T]['o'];

type NonObjectKeyValues<T> = {
  [K in NonObjectValuedKeys<T>]: {
    key: K;
    value: T[K];
  }
}[NonObjectValuedKeys<T>];

type ObjectKeyValues<T> = {
  [K in ObjectValuedKeys<T>]: MapKeys<T[K]>
}[ObjectValuedKeys<T>];

type MapKeys<T> =
  {
    v: NonObjectKeyValues<T>,
    k: ObjectKeyValues<T>;
  }['v' | 'k'];

interface IPerson {
  name: 'susan';
  children: {
    billy: string;
    sally: Date;
    deeper: {
      norma: RegExp,
    }
  };
}

MapKeys<IPerson> gives me

{
    key: "name";
    value: "susan";
} | {
    key: "billy";
    value: string;
} | {
    key: "sally";
    value: Date;
} | {
    key: "norma";
    value: RegExp;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM