1

Sorry if the title sounds confusing. Essentially what I am trying to do is that I want to make a function that takes an object and a key of that object, where the key might be nested inside of the object and has to be a specific type specified in the generics. So like

type Key = {
  key1: Key1;
};
type Key1 = {
  key2: Key2;
};
type Key2 = {
  status: Status;
};

type Status = "Active" | "Inactive";
interface MyObject {
  key: Key;
}

const myObject: MyObject = {
  key: {
    key1: {
      key2: {
        status: "Active"
      }
    }
  }
};


// 👇 this is the function I am trying to build
foo<MyObject, Status>(myObject, 'status')

Here I have a function foo, which takes two arguments. And the second argument is a key on the object(it could be nested so it is not necessarily on the initial level of the object) and it is of the type Status, in this case the key we can pass is status.

So if you change Status to Key2 then you can only pass key2 as the second argument because key2 is of the type Key2.

I am not sure if this is even possible with TypeScript. I guess I will need to do some sort of recursive generics but I am not exactly sure how to do it.

1
  • So you're looking for keyof, but recursive? Commented Nov 17, 2020 at 9:23

2 Answers 2

2

You can reclusively check if key's value extends the required type:

type KeysOfType<T, TValue> = {
    [K in keyof T]: T[K] extends TValue ? K : T[K] extends object ? KeysOfType<T[K], TValue> : never
}[keyof T];

declare function foo<TObject, TValue>(obj: TObject, key: KeysOfType<TObject, TValue>): void;

foo<MyObject, Status>(myObject, 'status')
foo<MyObject, Status>(myObject, 'bar') // Argument of type '"bar"' is not assignable to parameter of type '"status"'.

Playground

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

Comments

0

Perhaps this recursive type definition can you help you. It does deviate from your desired signature of foo.

Playground Link Given:

type Key = {
  key1: Key1;
};
type Key1 = {
  key2: Key2;
};
type Key2 = {
  status: Status;
};

type Status = "Active" | "Inactive";
interface MyObject {
  key: Key;
}

Go through the object's keys and if the value extends object, union that recursive type with the key that started the recursion. Otherwise, just take the key.

type FlattenNestedKeysToUnion<T> = {
  [K in keyof T]: T[K] extends object 
    ? FlattenNestedKeysToUnion<T[K]> | K
    : K
}[keyof T]

// Type instantiation is excessively deep and possibly infinite.(2589)
function foo<T extends MyObject>(o: T, key: FlattenNestedKeysToUnion<T>) {
  // ... logic 
}

However, tsc does complain that this type instantiation is excessively deep and possibly infinite for type T in FlattenNestedKeysToUnion<T>. I don't think there is a prescribed or idiomatic, way to solve that error besides something like the below hack which avoids generics and define foo like so:

function foo(o: MyObject, key: FlattenNestedKeysToUnion<MyObject>) {
  // ... logic 
}

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.