Reputation: 1651
In the following typescript example I have a function getPerson
that simply takes an object and a key to look up the value. However the input object is a Partial Generic:
export type Person = {
name: string;
age: string;
}
export type People = Record<string, Person>;
function getPerson<D extends People>(partialData: Partial<D>, key: string): Person {
if (!partialData.hasOwnProperty(key)) {
throw new Error("key not found in dataContainer.incompleteDataSet");
}
return partialData[key]; // TS Lint Error here
}
This code seems to work as I want it to, however I get a TS Lint Error in the return statement:
TS2322: Type 'D[string] | undefined' is not assignable to type 'Person'.
Type 'undefined' is not assignable to type 'Person
In the function getPerson
, the Generic D
should specify what kind of People
we have, e.g. D
could be a Parents
type with keys mom
and dad
.
export type Parents = {
mom: Person,
dad: Person,
}
const parentsData: Partial<Parents> = {
mom: {
name: "Ann",
age: "55",
}
}
const mom: Person = getPerson(parentsData, "mom");
console.log(mom);
However getPerson
should also be open to work with other Person-structures like this:
const grandParentsData: Partial<Grandparents> = getGrandParentsData();
const grandma: Person = getPerson(grandParentsData, "grandma");
The best way I found to express this was <D extends People>
next to getPerson
. That might cause my linting problem though, because D
would be allowed to have other key-values than just <string, Person>
as defined in People
. Am I right? Do you have any ideas how to do this better?
What I am looking for is not a better way to structure my data but to improve getPerson
. (In my real project it is impossible to change the data structures right now).
Here is link to a TypeScript Playground.
Thank you a lot!
Upvotes: 0
Views: 153
Reputation: 249486
partialData.hasOwnProperty
does not act as a type guard. (It can't act as one even in principle, since key
is dynamic)
The simplest solution is to use a not null assertion:
function getPerson<D extends People>(partialData: Partial<D>, key: string): Person {
if (!partialData.hasOwnProperty(key)) {
throw new Error("key not found in dataContainer.incompleteDataSet");
}
return partialData[key]!;
}
You can get a fully typed version, but it's semantics are a bit different:
function getPerson<D extends People>(partialData: Partial<D>, key: string): Person {
const value = partialData[key]
if (!value) {
throw new Error("key not found in dataContainer.incompleteDataSet");
}
return value;
}
The above version tests if the value of the property is not undefined. Indeed this seems to be a more type safe behavior, since just because the object has the property, it does not mean it is not undefined
(({ age: undefined }).hasOwnProperty ("age")
is true, but ({ age: undefined })['age']
is undefined)
Upvotes: 2