Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript function that takes an object without certain keys

Tags:

typescript

Playing around with trying to create a function that takes an a single argument object that must not have some set of keys, without making people specify types manually. I was trying to leverage never somehow, but I'm getting caught up in trying to understand how inference works for function arguments that take in a type with a generic parameter.

interface ReservedAttributes {
    someKey: string
}

// evaluates to never if the type has a key that intersects 
// with the keys in ReservedAttributes
type ValidAttributes<T> = keyof T extends Exclude<keyof T, keyof ReservedAttributes> ? T : never

// This is correctly a never, but it doesn't address this problem
type Test = ValidAttributes<{someKey : string}>


// This doesn't work because the function argument ends 
// up being inferred as { [name: string] : string}
function foo1<K extends { [name: string] : string}>(attributes: ValidAttributes<K>)  {
    // ...
}
foo1({a: 'hi', someKey: ''})

// Roughly equivalent to the above
function foo2< K extends { [name: string] : string}, V extends ValidAttributes<K> >(attributes: V)  {
    // ...
}

// This one allows V to correctly evaluate to never, 
// but I'm not sure how to leverage that
function foo3< K extends { [name: string] : string}, V extends ValidAttributes<K> >(attributes: K)  {
    // ...
}
foo3({a: 'hi', someKey: ''})

How would you solve this problem?

like image 341
Anthony Naddeo Avatar asked Oct 23 '25 09:10

Anthony Naddeo


1 Answers

I think I'd first try staying away from generics and conditional types and do something like this:

type ValidAttributes = Record<string, string> &
  Partial<Record<keyof ReservedAttributes, never>>;
declare function foo(attributes: ValidAttributes): void;

foo({ a: 'hi' }); // okay
foo({ a: 'hi', someKey: '' }); // error, someKey

In this case, ValidAttributes is a general string-valued dictionary but with the keys from ReservedAttributes listed as optional properties of type never (optional properties are allowed to be missing, and properties of type never are not really allowed to be present, so an optional property of type never is more or less one that must be missing.) Does that work for you?

If you need to use conditional types in a generic constraint it can be done:

type Attributes<K extends keyof any> = {
  [P in K]: P extends keyof ReservedAttributes ? never: string
};
declare function foo<T>(attributes: T & Attributes<keyof T>): void;
foo({ a: 'hi' }); // okay
foo({ a: 'hi', someKey: '' }) // error

But it's more complicated and achieves similar results. Hope that helps; good luck!

like image 143
jcalz Avatar answered Oct 25 '25 08:10

jcalz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!