2

In the following code...

interface Options {
  allowed?: string;
}

function test(options: Options) {
  return options;
}

const options = {
  allowed: 'allowed',
  notAllowed: 'notAllowed',
};

test(options); // does not throw an error

Why is adding notAllowed to options not throwing an error in Typescript when calling test(options)?

How can I write this so excess properties are disallowed?

EDIT 1...

I can get Typescript to throw an error if I rewrite it to const options: Options = {...};.

However, shouldn't typescript infer this because I've specified that the options parameter in the function should be of type Options?

If not, shouldn't typescript throw an error when trying to pass in const object = {...};?

Otherwise, this isn't very useful for other developers passing in their own objects into the test function.

EDIT 2...

The reason I want this is for option bags where I don't want developers to misspell a property. For instance, I want them to be able to provide an include property and disallow the includes property. Is the only way to make this happen when they pass in an object variable (instead of an object literal) as a parameter in the test function is to declare a property on the Options interface includes?: null; or something like that?

nratter
  • 397
  • 4
  • 14
  • Specify the type of your options variable: `const options: Options = { ... };` – JB Nizet Dec 07 '19 at 16:53
  • Thanks JB. I've updated my question to respond to your comment. – nratter Dec 07 '19 at 16:59
  • Does this answer your question? [Passing incorrect variable type to function do not emit type error](https://stackoverflow.com/questions/59124579/passing-incorrect-variable-type-to-function-do-not-emit-type-error) – jperl Dec 07 '19 at 17:03
  • 1
    No. When it compiles `const options = ...`. It says: oh, OK, this is an object without any declared type, i.e. with the anonylous, implicit type `{allowed: string; notAllowed: string;}`. Fine with me. And when it compiles `test(options)`, it says: no problem: the structure of `{allowed: string; notAllowed: string;}` is compatible with Options. What matters for the function is that it receives an object with all the required properties, of the good type. And it does. Why would that be a problem? – JB Nizet Dec 07 '19 at 17:03
  • Possible duplicate of [Is there an alternative to Partial to accept only fields from another type and nothing else?](https://stackoverflow.com/questions/56606614/is-there-an-alternative-to-partial-to-accept-only-fields-from-another-type-and-n) – jcalz Dec 07 '19 at 20:56
  • @JBNizet in my case, if extra fields were passed, all fields are being passed to database and my orm throws an error. – eugenedrvnk Jan 22 '23 at 02:26

3 Answers3

9

The issue is that TypeScript doesn't really have exact types in which properties are restricted only to those explicitly declared. Excess property checking, which is what you want, only happens in the specific situation where you use a fresh object literal.

One way to try to deal with this is to make your test() function generic in a way that fails to compile if it notices extra properties. The answer to the linked duplicate question explains how this works. Observe:

type Exactly<T, U> = T & Record<Exclude<keyof U, keyof T>, never>;
function test<T extends Exactly<Options, T>>(options: T) {
    return options;
}

const okay = { allowed: "okay" };
test(okay); // no error

const excess = {
    allowed: 'allowed',
    notAllowed: 'notAllowed',
};
test(excess); // error

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
2

How can I write this so excess properties are disallowed?

You can't. Typescript inherits duck typing of JavaScript, and a duck is still a duck, even though it has different feathers, as it quacks.

Or in other words: The object inside of options is an Options object as it has all the properties required by Options (none).

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • This is not strictly true. Excess property checking is performed on "fresh literals" as @jcalz points out in his answer. – Coderer Sep 09 '20 at 15:28
-1

If you are building the test function for other developers to use later then it's better not to use interfaces in parameter typing because it won't work unless you type the object before you pass it to test() function. The best thing to do here is to use optional parameters:

function test(allowed?: string) {
  return { allowed };
}

const options = {
  allowed: 'allowed',
  notAllowed: 'notAllowed',
};

test(); //works fine
test(options.allowed); //works fine
test(options.allowed, options.disallowed);  //error
MEDZ
  • 2,227
  • 2
  • 14
  • 18