简体   繁体   中英

TypeScript: Recursively mark single named property as required

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.

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;
} */

Playground link to code

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