Typescript deep replace multiple types

被刻印的时光 ゝ 提交于 2021-02-07 09:55:41

问题


I use mongodb with @types/mongodb. This gives me a nice FilterQuery interface for my mogodb queries for a collection of shaped documents. In my domain object class I have some extra logic like converting dates to moment objects or floats to BigNumber objects.

For my queries I need to convert these back, so for example a Moment object needs to be converted to a date object and so on. To avoid duplication and maintaining a separate interface (just for the queries) I thought of using mapped types to replace all types of Moment to type of Date

type DeepReplace<T, Conditon, Replacement> = {
  [P in keyof T]: T[P] extends Conditon
    ? Replacement
    : T[P] extends object
    ? DeepReplace<T[P], Conditon, Replacement>
    : T[P];
};


class MyDoaminClass {
  date: Moment;
  nested: {
    date: Moment;
  };
}

const query: DeepReplace<MyDoaminClass, Moment, Date> = {
  date: moment().toDate(),
  nested: {
    date: moment().toDate()
  }
};

This basically works, but I have about 4-5 of these types that I would need to replace. Is there an elegant way to chain several DeepReplace Types or even better: Specify all type replacements in one place? I would like to avoid something like type ReplaceHell = DeepReplace<DeepReplace<DeepReplace<MyDoaminClass, Moment, Date>, BigNumber, number>, Something, string>


回答1:


Assuming you want to do the replacement "all at once" and not as a "chain" (meaning that you don't intend to, say, replace X with Y and then replace Y with Z), then you can rewrite DeepReplace to take a union M of mapping tuples corresponding to [Condition1, Replacement1] | [Condition2, Replacement2] | .... So your old DeepReplace<T, C, R> would be DeepReplace<T, [C, R]>. The definition would look like this:

type DeepReplace<T, M extends [any, any]> = {
    [P in keyof T]: T[P] extends M[0]
    ? Replacement<M, T[P]>
    : T[P] extends object
    ? DeepReplace<T[P], M>
    : T[P];
}

where Replacement<M, T> finds the mapping tuple in M where T is assignable to the condition and returns the corresponding replacement, and is defined like this:

type Replacement<M extends [any, any], T> =
    M extends any ? [T] extends [M[0]] ? M[1] : never : never;

Let's see if it works on some types I'll make up here. Given the following:

interface DateLike {
    v: Date;
}
interface StringLike {
    v: string;
}
interface NumberLike {
    v: number;
}

interface Original {
    a: {
        dat: DateLike;
        str: StringLike;
        num: NumberLike;
        boo: boolean
    },
    b: {
        arr: NumberLike[]
    },
    c: StringLike,
    d: number
}

Let's replace the ...Like types:

type Replaced = DeepReplace<Original, 
  [DateLike, Date] | [StringLike, string] | [NumberLike, number]
>

/* equivalent to
type Replaced = {
    a: {
        dat: Date;
        str: string;
        num: number;
        boo: boolean;
    };
    b: {
        arr: number[];
    };
    c: string;
    d: number;
}
*/

So that works.


Please note that calling the new DeepReplace<T, [C, R]> this probably has the same edge cases the original DeepReplace<T, C, R> has. For example, unions like {a: string | DateLike} won't be mapped. I'll consider any tweaking of these to be outside the scope of the question.


Okay, hope that helps; good luck!

Playground link to code



来源:https://stackoverflow.com/questions/60437172/typescript-deep-replace-multiple-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!