snort
snort

Reputation: 2485

How can I constrain property values to keys in the current type?

In typescript, I want to declare an extensible interface via an indexer, to allow additional functions to be associated dynamically, but have sub properties that refer back to those indexed functions by name. Something tells me this may be beyond the acrobatics the TS compiler is capable of, but I've rarely been able to stump it. Any ideas?

interface Foo {
  someProp: {
    [x: string]: keyof this; // doesn't work... but something like this to refer to indexed keys?
  };
  [x: string]: Function; // client can provide whatever functions they want in here
}

// usage
let myFoo: Foo = {
   bar: {
      validFuncName: "someFooFunc", // should work
      invalidFuncName: "thisAintNoFunc" // should error!
   },
   someFooFunc: () => {}
}

Upvotes: 0

Views: 74

Answers (2)

Sly_cardinal
Sly_cardinal

Reputation: 13043

This is a variant of Tobias S.'s answer (and Kelly's original snippet) that maintains the type of the object passed to the helper function.

type ExtractFunctions<T> = {
    [K in keyof T as T[K] extends Function ? K : never]: T[K]
}

function helper<
    T extends { 
        [K in keyof T]: K extends "bar"
            ? Record<string, keyof ExtractFunctions<T>>
            : T[K]
    }
>(obj: T): T {
    return obj;
}

helper({
   bar: {
      validFuncName: "someFooFunc", // ok
      validFuncName2: "someOtherFunc", // ok
      invalidFuncName: "thisAintNoFunc" // Error
   },
   someFooFunc: () => {},
   someOtherFunc: () => {}
});

Upvotes: 1

Tobias S.
Tobias S.

Reputation: 23925

Based on @kelly's answer, here is a slightly less "over engineered" solution.

function helper<T extends Record<string, any>>(obj: { 
   [K in keyof T]: K extends "bar" 
     ? Record<string, Exclude<keyof T, "bar">> 
     : Function 
}){}

helper({
   bar: {
      validFuncName: "someFooFunc", // ok
      validFuncName2: "someOtherFunc", // ok
      invalidFuncName: "thisAintNoFunc" // Error
   },
   someFooFunc: () => {},
   someOtherFunc: () => {}
})

Playground

Upvotes: 2

Related Questions