The backend returns some optional fields but you want to write a function that makes those optional fields nullable. Why? No apperent reason.
type Response = {
name: string;
age?: number;
};
First step is to write a key mapper, i.e. I want a generic that takes two objects and returns a map of the common keys that don't have the same type, so you can use &
type MappedC<A, B> = {
[K in keyof A & keyof B]: A[K] extends B[K] ? never : K
};
// example
type M = MappedC<{ a: number }, { a: string }>; // {a: 'a'}
type O = MappedC<{ a: number }, { a: number }>; // {a: never}
type D = MappedC<{ a: number }, { b: number }>; // {}
Second step is finding all the optional keys!
type Optional<T> = MappedC<T, Required<T>>[keyof T];
type OptionalKeys = Optional<Response>; // 'age'
The -
removes properties such as readonly
or ?
.
For example do you want to have the same but writable and optionals? You can just do:
type Writeable<T> = { -readonly [P in keyof T]-?: T[P] };
The ternary operator on the other hand is always coupled with extends
and allows you do "split" a type based on another one.
So now the final type is:
export type FromOptionalToNullable<T extends {}> = {
[K in keyof T]-?: K extends OptionalKeys<T> ? T[K] | null : T[K]
};
type ParsedResponse = FromOptionalToNullable<Response>; //
type ParsedResponse = {
name: string;
age: number | number;
};
The extends
is basically a conditon check on whether the second element of the condition is containd in the first. It is often used as a ===
;
The infer
is a way to assign a name to an unknown type and let TS do the rest (extremely cool and powerful btw);
type Infer<O> = O extends { whatIsThis: infer A } // Returns the type of `whatIsThis` in an object
? A
: never;
type WhatIsThis = Infer<typeof { whatIsThis: "findMe!" }>; // string
// Functions
type InferArgument<F> = F extends (...input: infer I) => infer R ? [A, R] : never
type Input = InferArgument<typeof (first: number, second: string) => true> // [[number, string], booolean]
// Promises
type PromiseInfer<I> =
I extends Promise<infer G>
? G
: never
Some playground here
Now basically I don't care about what the type in the "backend" is, I want to write a function I can start from the signature and be let typescript guide me in the process.
const parseResponse = (resp: Response): ParsedResponse => {
const parsed = Object.entries(resp).reduce<ParsedResponse>(
(p, [k, v]) => ({ ...p, [k]: v || null }),
{}
);
return parsed;
};
type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never; // Find the type of a tuple head
type Tail<T extends any[]> = ((...t: T) => any) extends ((
_: any,
...tail: infer TT
) => any)
? TT
: [];
type HasTail<T extends []> = T extends ([] | [any]) ? false : true;
type Curry<P extends any[], R> =
<T extends any[]>(...args: T) =>
HasTail<P> extends true ?
? Curry<Tail<T>, R>
: R
You can get lost in this stuff man. But if you want to know more just check this out