Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to restrict typescript object to contain only properties defined by its class?

Tags:

typescript

Here is my code

async getAll(): Promise<GetAllUserData[]> {     return await dbQuery(); // dbQuery returns User[] }  class User {     id: number;     name: string; }  class GetAllUserData{     id: number; } 

getAll function returns User[], and each element of array has the name property, even if its return type is GetAllUserData[].

I want to know if it is possible "out of the box" in TypeScript to restrict an object only to properties specified by its type.

like image 373
Valera Avatar asked Mar 30 '18 20:03

Valera


People also ask

How do I make object properties optional in TypeScript?

To make a single property in a type optional, create a utility type that takes a type and the property name as parameters and constructs a new type with the specific property marked as optional.

How do you access the properties of objects in TypeScript?

To dynamically access an object's property: Use keyof typeof obj as the type of the dynamic key, e.g. type ObjectKey = keyof typeof obj; . Use bracket notation to access the object's property, e.g. obj[myVar] .

How do you omit a property in TypeScript?

Use the Omit utility type to exclude a property from a type, e.g. type WithoutCountry = Omit<Person, 'country'> . The Omit utility type constructs a new type by removing the specified keys from the existing type. Copied!

What is property in TypeScript?

Property in TypeScriptA property of a function type for each exported function declaration. A property of a constructor type for each exported class declaration. A property of an object type for each exported internal module declaration.


1 Answers

I figured out a way, using built-in types available since TypeScript version 3, to ensure that an object passed to a function does not contain any properties beyond those in a specified (object) type.

// First, define a type that, when passed a union of keys, creates an object which  // cannot have those properties. I couldn't find a way to use this type directly, // but it can be used with the below type. type Impossible<K extends keyof any> = {   [P in K]: never; };  // The secret sauce! Provide it the type that contains only the properties you want, // and then a type that extends that type, based on what the caller provided // using generics. type NoExtraProperties<T, U extends T = T> = U & Impossible<Exclude<keyof U, keyof T>>;  // Now let's try it out!  // A simple type to work with interface Animal {   name: string;   noise: string; }  // This works, but I agree the type is pretty gross. But it might make it easier // to see how this works. // // Whatever is passed to the function has to at least satisfy the Animal contract // (the <T extends Animal> part), but then we intersect whatever type that is // with an Impossible type which has only the keys on it that don't exist on Animal. // The result is that the keys that don't exist on Animal have a type of `never`, // so if they exist, they get flagged as an error! function thisWorks<T extends Animal>(animal: T & Impossible<Exclude<keyof T, keyof Animal>>): void {   console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`); }  // This is the best I could reduce it to, using the NoExtraProperties<> type above. // Functions which use this technique will need to all follow this formula. function thisIsAsGoodAsICanGetIt<T extends Animal>(animal: NoExtraProperties<Animal, T>): void {   console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`); }  // It works for variables defined as the type const okay: NoExtraProperties<Animal> = {   name: 'Dog',   noise: 'bark', };  const wrong1: NoExtraProperties<Animal> = {   name: 'Cat',   noise: 'meow'   betterThanDogs: false, // look, an error! };  // What happens if we try to bypass the "Excess Properties Check" done on object literals // by assigning it to a variable with no explicit type? const wrong2 = {   name: 'Rat',   noise: 'squeak',   idealScenarios: ['labs', 'storehouses'],   invalid: true, };  thisWorks(okay); thisWorks(wrong1); // doesn't flag it as an error here, but does flag it above thisWorks(wrong2); // yay, an error!  thisIsAsGoodAsICanGetIt(okay); thisIsAsGoodAsICanGetIt(wrong1); // no error, but error above, so okay thisIsAsGoodAsICanGetIt(wrong2); // yay, an error! 
like image 171
GregL Avatar answered Oct 23 '22 08:10

GregL