Simpler way to check literal object type in TypeScript

六眼飞鱼酱① 提交于 2020-04-11 11:41:43

问题


I want to Object.assign to an object of a known type, a set of properties from an object literal that should be of the same type. Is there a cleaner way to do this in TypeScript, without actually calling an identity function, or creating a separate variable, as suggested in this related question?

type Person = {
  first?: string;
  last?: string;
}

// Overkill, actually generates code to call a function
function check<T>(value: T): T { return value; }

const dude: Person = {
  first: 'Mike',
};

Object.assign(dude, check<Person>({  // <- trying not to call a function
  last: 'Johnson',
  age: 27,  // <-- should be flagged as unknown property
}));

TL;DR - looking for a way to type check an object literal directly.


回答1:


I can't think of a "perfect" solution to this that works in all circumstances. Most of the mechanisms that convince the compiler to perform the type checking you want are also accompanied by some added runtime code (such as a new intermediate variable assignment, or calling an identity function), which you've said you don't want.


One problem is that TypeScript doesn't have inline type annotations. (See microsoft/TypeScript#7481 and microsoft/TypeScript#13208 for discussion.) You'd like to be able to ask the compiler to verify that an expression {...} is of type Person, and have the compiler complain if it cannot be verified. The closest operator we have in TypeScript is a type assertion of the form {...} as Person. But this tells the compiler that the expression {...} is of the type Person; you want to ask.


Even if we had an inline annotation operator, there's another problem: object types in TypeScript are not exact. (See microsoft/TypeScript#12936 for discussion.) Object types in TypeScript are open in that you can add more properties to them without breaking compatibility. If you have an object of type Person, you know something about its first and last properties, but you really don't know anything about any other properties. Just because the definition of Person doesn't mention an age property, it does not mean that an object of type Person cannot have an age property. There could well be an interface like this:

interface AgedPerson extends Person {
  age: number;
}

The structural nature of the TypeScript type system means that {last: "Johnson", age: 27} is a valid AgedPerson even if you don't declare it as such (and even if AgedPerson is not defined). And since AgedPerson is a valid subtype of Person, then {last: "Johnson", age: 27} is a valid Person also.

Now, when people use object literals like {last: "Johnson", age: 27} they usually don't intend to add such extra properties, so TypeScript has a feature called excess property checking which treats object literals as if they were of exact types and complains if you add unknown properties. This feature is useful, but it's very easy to circumvent. So it's important to mention that if you refactor your code at all, the warning about age being an excess property might disappear:

const ageDude = { last: 'Johnson', age: 27 };
const personDude: Person = ageDude; // no error

That being said, for the particular example you gave, the solution I'd recommend would be:

Object.assign<Person, Person>(dude, {
  last: 'Johnson',
  age: 27, // error
});

Here you are manually specifying the generic type parameters on the call to Object.assign, where the first type parameter corresponds to the type of the first function argument, and the second type parameter corresponds to the type of the second function argument. You want the compiler to treat both of those as Person, so you should write Object.assign<Person, Person>(...). And the expected excess property error appears.


Okay, hope that helps; good luck!

Playground link to code




回答2:


Why is dude a constant if you're going around it with Object.assign?

Usually, if you try to assign anything that is not a 'Person' to dude, you will get an error e.g.

type Person = {
  first?: string;
  last?: string;
}

let dude: Person = {
  first: 'Mike',
};

dude = {  
   last: 'Johnson',
   age: 27
} // Error: Not assignable to type Person

This will type-check the assignment and also show an error if you wanted to assign to a constant.



来源:https://stackoverflow.com/questions/61106889/simpler-way-to-check-literal-object-type-in-typescript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!