问题
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