简体   繁体   中英

Typescript: Input object has subset of keys of an interface

I want to be able to specify that an input object to a function should only contains keys that are present in an existing interface (it does not need to have all the keys).

interface Group {
  name: string
}

interface Person {
  id: number
  name: string
  email: string
  age: number
}

interface AddedProperties {
  groups: Group[]
}

function createPerson<D extends object>( // <-- Should convey, D is an object with some keys of Person
  personData: D
): D & AddedProperties {
  return Object.assign({}, personData, { groups: [] })
}

interface NameAndAge {
  name: string
  age: number
}

// Valid: Should be valid
const person: NameAndAge = createPerson({ name: "bob", age: 5 })
// Valid: Should be invalid as 'personality' doesn't exist on Person
const person2 = createPerson({ name: "bob", age: 5, personality: "cheerful" })

Is this possible in typescript?

A simple approach is using Partial<> :

function createPerson(
  personData: Partial<Person>
): Partial<Person> & AddedProperties {
  return { ...personData, groups: [] };
}

Partial takes a type and makes all of its members optional, thus allowing you to specify any subset of properties of that type.

The downside of this typing is that the returned type has no knowledge of what you put into it:

createPerson({name: "Bob", age: 5});         // OK
createPerson({name: "Bob", gender: "male"}); // Type error
createPerson({name: "Bob"}).name;            // OK
createPerson({name: "Bob"}).age;             // OK :-(

If you want to avoid this as well, check out my other answer.

OK, I've found the better answer now after some research :-) I'll leave my other answer as well, though. Here's what you can do:

function createPerson<D extends keyof Person>(
    personData: Pick<Person, D>
): Pick<Person, D> & AdditionalProperties {
    return Object.assign({}, personData, {groups: []});
}

This allows personData to be only a strict subset of Person , and the return type will be exactly the same structure plus AdditionalProperties :

createPerson({name: "Bob", age: 5});         // OK
createPerson({name: "Bob", gender: "male"}); // Type error
createPerson({name: "Bob"}).name;            // OK
createPerson({name: "Bob"}).age;             // Type error

How does it work? The generic D extends keyof Person would, once again, allow adding new fields, which we want to avoid. We cannot handle this in the definition of the generic type argument (since we only have extends there), but instead limit the actual function argument to

personData: Pick<Person, D>

This restricts the input to only those properties of Person which appear in D . We use the same type as the return type (intersected with AdditionalProperties ).


You can see it in action here :

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM