Aidin
Aidin

Reputation: 30097

TypeScript: Inherit the type of values, not all the keys, from a Record<> interface

I have a main interface in my project which dictates the value type of the properties of the extending objects.

For the sake of simplicity, let's assume it is like this:

interface Printable extends Record<PropertyKey, string> {
}

It just says all the values should be string. And it properly prohibits its extending interfaces to have a number key, like below.

interface Receipt extends Printable {
    customerName: string;
    // customerId: number;  // EXPECTED: This line errors if uncommented (Property 'customerId' of type 'number' is not assignable to string index type 'string'. (2411))
}

However, the unwanted side-effect is that it widens the "key" range to be "any string", so it doesn't catch the following error:

const r: Receipt = { customerName: "Jack" };
console.log(r.address); // UNEXPECTED: This line DOESN'T error "for Property 'address' does not exist on type 'Receipt'.(2339)"

Typescript Playground Link

Question

How can I get the "enforced value type" benefit from a super-interface, without the unwanted "widened key range"?


PS. It's different from TypeScript: Create typed Record without explicitly defining the keys in the sense that here I want an interface with no overhead on the objects or their instantiation. Please stop reporting it as a duplicate. :)

Upvotes: 2

Views: 1821

Answers (2)

Aleksey L.
Aleksey L.

Reputation: 37996

You can create utility type based on generic type constraint to validate input type values:

type Printable<T extends Record<PropertyKey, string>> = T;

type Receipt = Printable<{
    customerName: string;
    // customerId: number; // Expect error
}>

const r: Receipt = { customerName: "Jack" };
console.log(r.address); // Property 'address' does not exist on type '{ customerName: string; }'.

Playground

Upvotes: 3

Mahdi Ghajary
Mahdi Ghajary

Reputation: 3253

I don't think it's possible at least now, because you have to do something like this:

interface Printable extends Record<keyof Printable, string> {

}

Which gives an error regarding the recursive use of Printable interface and doesn't make much sense tbh. the only solution that comes to my mind, is like below.

type StringOnly<T extends string> = Record<T, string>

type Printable = StringOnly<"key1" | "key2" | "key3">

interface Receipt extends Printable {
    customerName: string;
}

const r: Receipt = { customerName: "Jack"};
console.log(r.address); // Property 'address' does not exist on type 'Receipt'.(2339)

Upvotes: 0

Related Questions