3

Case 1: No errors detected when "randomKey" is passed down to doMethod.

interface Options {
 bar: string;
 foo?: number;
}

function doMethod(options: Options) {
 console.log(options);
}


const options = {
 bar: 'bar',
 foo: 123,
 randomKey: 231,
}
doMethod(options); // No errors appear!
console.log(options);

Case 2: Errors only appears after when options are directly passed into the function

interface Options {
 bar: string;
 foo?: number;
}

function doMethod(options: Options) {
 console.log(options);
}

doMethod({
 bar: 'bar',
 foo: 123,
 randomKey: 231, // ERROR: "Object literal may only specify known properties, and 'randomKey' does not exist in type 'Options'."
});

console.log(options); // In this scenario, I cannot console log options

The silent error is tough to find. You can easily misspell a word for example, and things might not happen they way you want it.

What happens when you misspell "foo", like "fooo"? The silent error would still occur. You expect "foo" to trigger something but would not in this case if you spelt "fooo".

3
  • 1
    Yep, that's how TS works, it's "as designed". Commented Nov 28, 2019 at 10:28
  • @zerkms do you recall some documentation about that? Its quite interesting, I suppose this behavior is related to the fact that people use such object literals as labeled function arguments. That is why it is related to calling the function with argument which this function those not have. Commented Nov 28, 2019 at 10:43
  • 1
    The concept is call Freshness, check this link Commented Nov 28, 2019 at 11:16

2 Answers 2

4

TypeScript provides a concept of Freshness (also called strict object literal checking) to make it easier to type check object literals that would otherwise be structurally type compatible.

-- Source TypeScript Deep Dive

Follow the link above, it's explains quite clear on this topic.

Here I'll explain briefly with your scenario.

// case 1: no error
const options = { bar: 'bar', foo: 123, randomKey: 231 };
doMethod(options);

// case 2: error
doMethod({ bar: 'bar', foo: 123, randomKey: 231 });

In runtime JavaScript, both use case should just work. That's true because JS is weak-typed, extra keys in an object cause no harm in runtime.

Back to TS, you specify that doMethod(options) shall accept options that has the shape { bar: string; foo?: number }. This is stating your minimum requirement for the input to doMethod. Input with extra keys? No problem, that's how JS works.

Then, why in the world would TS throw error on you in case 2?

Because TS want you to know that extra randomKey is unnecessary! Even though it does no harm.

This is the idea behind the freshness concept. An analog would be like:

function add(a: number, b: number) { return a + b };

add(1, 2, 3); // TS Error

Though in JS runtime, that usage still work, you probably don't mean that. So TS warns you about it ahead of time.


To answer the second part of your question, what if you just want to preserve the freshness of object, what if you want the exact shape of input to be strictly checked? There is a solution with drawback:

type ExactShape<T, R> = T extends R
  ? Required<R> extends T
    ? T
    : never
  : never

function doMethod<T>(options: ExactShape<T, Options>) {
   console.log(options);
}

TypeScript Playground Live Demo

This time TS will throw error like you wish. But the drawback is, when mouse hover on error, it will no longer tell useful info why you're wrong.

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

4 Comments

Good answer! Although how to solve it when you misspell something by accident? In complex applications, it would take very long before noticing a super tiny spelling mistake. Typescript by nature should eliminate or warn us about this?
In your case mispell the foO key might cause problem. But you have to accept this drawback. Since you mark it as optional, then your function should work with or without foo. So, maybe avoid using optional keys if they're not misspell-resilient?
True. Writing more explicitly could be an option. Writing two functions, one with foo, and one without foo.
@Richard There's a solution to your need, check the updated part of my answer.
-1

There's no problem. It's normal behavior. Think about the interface as promise to provide the data.

It's important to remember that typescript at the end of the day is compiled to vanilla javascipt.

As long as you provide all data for the interface it doesn't matter if the actual object you provide contains extra information.

Good example is

interface Options {
 bar: string;
 foo?: number;
 randomKey: number
}

const doMethod =(options: Options): any=> {
 console.log(options);
}


const options = {
 bar: 'bar',
 foo: 123
}
doMethod(options); ** error appears!**
console.log(options);

To get better typesafe experience you might want to use interface for the object as well.

const options: Options = { bar: 'bar', foo: 123 } 

That way you will be forced to comply with interface. And get error in case you try to add unexpected extra data

3 Comments

The silent error is tough to find. You can easily misspell a word for example, and things might not happen they way you want it. In your case, what happens when you misspell "foo", like "fooo"? The silent error would still occur. And "foo" would trigger something but did not.
Think about the interface as a promise to have the data. You might provide extra data and there will not be any problem, but if you don't data promise you get error .
Suggestion would be to use const options: Options = { bar: 'bar', foo: 123 } That will provide you with the type checking

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.