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.