Given a type like:
type Book = {
__typename?: 'Book';
author?: {
__typename?: 'Author';
name?: string;
};
};
I want to remove the optional flag from the __typename
properties, recursively. Resulting in:
type TypedBook = DeepMarkRequired<Book, "__typename">;
// type TypedBook = {
// __typename: 'Book';
// author?: {
// __typename: 'Author';
// name?: string;
// };
// };
ts-essentials
has MarkRequired, but it's not recursive, and when I try a simple implementation like:
type DeepMarkRequired<T extends object, K extends PropertyKey> = MarkRequired<
{ [P in keyof T]: T[P] extends object ? DeepMarkRequired<T[P], K> : T[P] },
K
>;
TypeScript complains on line 3 that K
doesn't satisfy keyof T
, which makes sense since Required
and Pick
both require keyof T
rather than PropertyKey
to allow this to be generic. I'm not sure how to allow passing an arbitrary parameter name all the way down the tree.
CodePudding user response:
You can build this yourself with a combination of Pick
, Required
, and Omit
utility types, but the piece you're missing is that you need to do something like Pick<T, Extract<keyof T, K>>
with the Extract<T, U>
utility type if you're not sure whether K
is a key of T
or not.
But I'd prefer to use key remapping to write this, like:
type DeepMarkRequired<T, K extends PropertyKey> = (
{ [P in keyof T as Extract<P, K>]-?: DeepMarkRequired<T[P], K> } &
{ [P in keyof T as Exclude<P, K>]: DeepMarkRequired<T[P], K> }
)
This yields
type TypedBook = DeepMarkRequired<Book, "__typename">;
/* type TypedBook = {
__typename: "Book";
} & {
author?: DeepMarkRequired<{
__typename?: "Author" | undefined;
name?: string | undefined;
} | undefined, "__typename">;
} */
which is the type you want. If you want it to look more like your version you can force the compiler to cash out all those aliases by copying the type to a new type parameter via conditional type inference and then do an identity map over that:
type DeepMarkRequired<T, K extends PropertyKey> = (
{ [P in keyof T as Extract<P, K>]-?: DeepMarkRequired<T[P], K> } &
{ [P in keyof T as Exclude<P, K>]: DeepMarkRequired<T[P], K> }
) extends infer O ? { [P in keyof O]: O[P] } : never
which yields
/* type TypedBook = {
__typename: "Book";
author?: {
__typename: "Author";
name?: string | undefined;
} | undefined;
} */