Reputation: 6841
I have a basic interface for things that may have Ids:
interface Identifiable {
id?: number;
}
And I have a generic function that converts a record object into a thing with id:
function fromRowToObj1<T extends Identifiable>(row: { id: number; }): Partial<T> {
return { id: row.id };
// Type '{ id: number; }' is not assignable to type 'Partial<T>'.
}
I understand that this happens because there are T
s that extend Identifiable
that would make that return statement illegal. For example, the type { id: undefined }
or { id: 1 }
. So I decided to tweak the return type a bit to enforce a numeric id:
type Identified<T extends Identifiable> = {
[K in keyof T]?: K extends "id" ? number : T[K];
}
// Should give something like type C = { id?: number | undefined; ... }
function fromRowToObj2<T extends Identifiable>(row: { id: number; }): Identified<T> {
return { id: row.id };
// Type '{ id: number; }' is not assignable to type 'Identified<T>'.
}
Why, though? Which possible T
(such that T extends Identifiable
) makes it so { id: number }
is not assignable to Identified<T>
?
If there's no way to adjust the Identified
type to make this work, is there another way to type the conversion function to work with generic subtypes of Identifiable
?
Upvotes: 1
Views: 128
Reputation: 2640
The issue you are facing is thoroughly described here. As you noticed yourself there are subtypes of T extends Identifiable
which renders your return value { id: row.id }
invalid. For example Identified<{id?: never}>
will never be valid for { id: row.id }
. Never
is still a valid type for id
because you declared all keys of Identified
as optional. Identified<T>
is actually equal to Partial<T>
if T extends Identifiable
. Typescript correctly throws an error here. Though, you can still work around that if you set valid default values from which you can work onwards (playground):
interface Identifiable {
id?: number;
}
// results in optional id
function fromRowToObj1<T extends Identifiable>(row: { id: number; }) {
const result: Partial<T> = {} // valid for all subtypes of Partial<T>
result.id = row.id
return result;
}
// results in non optional id
function fromRowToObj2<T extends Identifiable>(row: { id: number; } ) {
const partial: Partial<T> = {}; // valid for all subtypes of Partial<T>
const result = {
...partial,
id: row.id
};
return result;
}
interface TestObject {
id: number,
arg1: string;
arg2: boolean;
}
const result1 = fromRowToObj1<TestObject>({id: 5});
result1.id // optional
result1.arg1 = "test" // intellisense works
result1.arg2 = true; // intellisense works
const result2 = fromRowToObj2<TestObject>({id: 5});
result2.id // not optional
result2.arg1 = "test" // intellisense works
result2.arg2 = true; // intellisense works
Upvotes: 1