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.
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.
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] .
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!
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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With