Giraphi
Giraphi

Reputation: 1651

TypeScript: Use of extend + Generic leads to Error in this example

In the following minimal example I get the typescript error: TS2339: Property 'creationDate' does not exist on type 'B[keyof B]. I hope you can help me to find a way to express this usecase in a better way, so that this error doesn't happen.

interface Entry<V> {
    value: V;
    creationDate: string
}

type Book = PaperBook | AudioBook;

interface PaperBook {
    title: Entry<string>;
    numPages: Entry<number>;
}

interface AudioBook {
    title: Entry<string>;
    duration: Entry<number>;
}

function getCreationDate<B extends Book>(book: B, key: keyof B) {
    return book[key].creationDate; // <- Error happens here
}

See this typescript playground

In getCreationDate() I am trying to access book[key].creationDate which gives an error. It looks like I need to somehow tell typescript that no matter what key we use to access my book object, I promise you will always find an Entry object there.

Maybe the problem lies in <B extends Book>? What i actually would want to express here is something like:

"B is either PaperBook or AudioBook" or "B is one of the types that underly the union type Book"

Hope this makes sense and you have a solution for me, thank you!

Upvotes: 0

Views: 55

Answers (2)

cyco130
cyco130

Reputation: 4934

It is not safe because B extends Book can have other keys. Consider the following (following the definitions in the original post):

const naughtyBook = {
    title: { value: "title", creationDate: "today" },
    numPages: { value: 200, creationDate: "yesterday" },
    badValue: "Not an entry",
}

getCreationDate(naughtyBook, "badValue");

This will clearly fail.

Upvotes: 0

Linda Paiste
Linda Paiste

Reputation: 42188

extends means that B could include additional properties which have any value, not necessarily an Entry. One way to fix this is to require that all values on B are an Entry like so:

function getCreationDate<B extends Book & Record<string, Entry<any>>>(book: B, key: keyof B) {
    return book[key].creationDate;
}

B extends the Record, so we don't actually widen the key type.

Typescript Playground Link

Normally I would say that you don't want a generic at all here and that you should just require book to be a Book, but with different keys on the two branches of the union that is difficult.

Upvotes: 2

Related Questions