Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
340 views
in Technique[技术] by (71.8m points)

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

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 it's type.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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!

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...