0

I want to extract a sub-type from an interface in typescript like below

interface components {
  schemas: {
    a: {
      b?: Array<{
        c?: {
          d?: string;
        };
      }>;
    };
  }
// I want to export

export type custom = components["schemas"]["a"]["b"][number]["c"]

however I get an error at [number] saying Type '{ c?: { d?: string | undefined; } | undefined; }[] | undefined' has no matching index signature for type 'number'.ts(2537)

How do we achieve this?

1
  • 2
    NonNullable<NonNullable<NonNullable<components["schemas"]["a"]>["b"]>[number]>["c"] ... does the trick but as you see extremely verbose - Required could also be used but I'd vie for a DeepRequired utility Commented Apr 13, 2022 at 13:46

2 Answers 2

1

Here is what I came up with:

type Increment<A extends number[]> = [...A, 0];

type DeepRequired<T, D extends number = -1, R extends number[] = []> =
T extends object ? R["length"] extends D ? T : {
    [K in keyof T]-?: DeepRequired<T[K], D, Increment<R>>;
} : T;

Let's look at DeepRequired without the extra bits:

type DeepRequired<T> = T extends object ? {
    [K in keyof T]-?: DeepRequired<T[K]>;
} : T;

This type is simple at its core; it's simply traversing the entire object and making all properties required.

This does work without errors:

type T = DeepRequired<components>["schemas"]["a"]["b"][number]["c"];

But you'll notice T's "d" property is required too, which might not be the desired result. That's why it's a lot more complicated; it limits the depth at which it makes properties required.

Back to our original snippet, a utility type Increment is created to increment the depth we are at, and the new parameters for DeepRequired include the specified depth (optional), and the current depth it is at.

Before it makes all properties required, it checks if it exceeds the depth specified, and if it does, it stops.

This now works as expected:

type T = DeepRequired<components, 5>["schemas"]["a"]["b"][number]["c"];

Playground

Sign up to request clarification or add additional context in comments.

Comments

1

I really like @kellys' answer for the part of the DeepRequired, but if the goal for the result is string | undefined, I don't like the part where a number (the depth) has to be hardcoded so I can suggest the following approach

type Path<T, K> = K extends [any, ...any]
  ? (K extends [keyof NonNullable<T>, ...infer RR]
    ? Path<NonNullable<T>[K[0]], RR>
    : never)
  : T


export type custom = Path<components, ['schemas', 'a', 'b', number, 'c', 'd']>

TS Playground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.