1

I've gotten this far: which seems to work

function test<types extends Record<string,any>>(dict: dictionary<types>){}

type dictionary<types extends Record<string, any>> = {
  [key in keyof types]: {
    bar?: types[key];
    foo?: (value:types[key])=>true;
  };
};

test({
 key1:{
  bar: 2,
  foo: (input:number)=>true,
 },
 key2:{
  bar: 'hello'
  foo: (input: number)=>true, // Error! "input" needs to be string
 }
})


BUT! I also need a generic type reference to the dict parameter. And for some reason, this doesn't work


function test2<
  types extends Record<string,any>,
  dictionary extends dictionary2<types> // <-- Added a generic type
>(dict: dictionary){}

// Same as above
type dictionary2<types extends Record<string, any>> = {
  [key in keyof types]: {
    bar?: types[key];
    foo?: (value:types[key])=>true;
  };
};

// Same as above
test2({
 key1:{
  bar: 2,
  foo: (input: number)=>true,
 },
 key2:{
  bar: 'hello', 
  foo: (input:number)=>true,// Should be an Error (but isn't)! "input" needs to be string
 }

Playground link

user1543574
  • 873
  • 1
  • 7
  • 12
  • Please consider [this](https://tsplay.dev/WJR3vW) example. [This](https://stackoverflow.com/questions/69909166/when-declaring-an-array-of-generic-items-how-can-i-allow-the-generic-parameter/69909668#69909668) is related question. There are drawbacks. You need to use methods because they are bivariant. Let mew know if it works. If you know upfront your values, you can use [this](https://catchts.com/callbacks#callback_in_union%202) example. However, the best approach is to use builder function. It is easy, readable and safe – captain-yossarian from Ukraine Dec 05 '21 at 20:00
  • Builder example-> [here](https://tsplay.dev/w2EobW)) – captain-yossarian from Ukraine Dec 05 '21 at 20:06

2 Answers2

1

You could do like this:

function test2<T extends Record<string, unknown>>(dict: Dictionary<T>) { }

type Dictionary<T> = {
  [key in keyof T]: {
    bar?: T[key];
    foo?: (value: T[key]) => true;
  };
}

// Same as above
test2({
  key1: {
    bar: 2,
    foo: (input: number) => true,
  },
  key2: {
    bar: 'hello',
    foo: (input: number) => true, // Actual error
  }
});

TypeScript playground

Guerric P
  • 30,447
  • 6
  • 48
  • 86
0

Change the inference scope so that types is inferred and dict is typed based on that inference rather than a second type parameter, i.e.,

function test2<
  types extends Record<string,any>
>(dict: dictionary2<types>){}

Working playground here.

EDIT: Example usage with conventional capitalization

function test2<
  T,
>(dict: Dictionary<T>){

  type FullDictionary = Dictionary<T> // can't you just use this in your function?

}

type Dictionary<T extends Record<string, any>> = {
  [K in keyof T]: DictionaryEntry<T[K]>
}

type DictionaryEntry<T> = {
  bar?: T
  foo?: (value:T)=>true
}

test2({
 key1:{
  bar: 2,
  foo: (input: number)=>true,
 },
 key2:{
  bar: 'hello', 
  foo: (input:number)=>true
 }
})
sam256
  • 1,291
  • 5
  • 29
  • Correct me if I'm wrong, but this is the same as the code I currently have isn't it (i.e. given at the top of my question) – user1543574 Dec 05 '21 at 18:39
  • oh, ha. but then i guess i'm confused at what your question is. why can't you just use `dictionary`? btw, its common to write this code with capitalized types for readability, e.g.: `function test` and `type Dictionary =` etc. – sam256 Dec 05 '21 at 18:43
  • I need a generic reference to the `dictionary` type to check if certain properties exist – user1543574 Dec 05 '21 at 18:51
  • but anywhere you want to use that generic, can't you just use dictionary? i've edited my answer to show what i mean. in my edited answer, isn't `FullDictionary` giving you what you want? what what a generic type parameter allow you to do that you can't do with that? (sorry if i'm being dense) – sam256 Dec 05 '21 at 19:13