Giraphi
Giraphi

Reputation: 1651

Why do I get a type inference error in this example and how to do it better?

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

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

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]!;
}

Playground Link

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;
}

Playground Link

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

Related Questions