I would like my function to take any kind of objects but if the objet have a property 'id' making sure that is is either a string or a number.
Here is the minimal example:
interface FnItem {
id?: string | number;
};
function fn<T extends FnItem>(item: T, callback: (item: T) => void) {
console.log(item.id);
callback(item)
};
fn({ name: 'Michel' }, item => item.name);
fn({ name: 'Michel', id: 12 }, item => item.name);
It throws this error
Argument of type '{ name: string; }' is not assignable to parameter of type 'FnItem'.
Object literal may only specify known properties, and 'name' does not exist in type 'FnItem'
---
Property 'name' does not exist on type 'FnItem
CodePudding user response:
Assuming that FnItem
might be either any object with any properties or any object where id
is number|string
I would rather stick with this solution:
type FnItem = Record<string, unknown>
type IdValidation<Obj extends Record<string, unknown>> =
Obj extends { id: infer Id } ? Id extends string | number ? Obj : Obj & { id: never } : Obj;
function fn<T extends FnItem,>(item: IdValidation<T>, callback: (item: IdValidation<T>) => void) {
console.log(item.id);
callback(item)
};
fn({ name: 'Michel' }, item => item.name);
fn({ name: 'Michel', id: 12 }, item => item.id);
fn({ name: 'Michel' }, item => item.ya); // error
fn({ name: 'Michel', id: [] }, item => item.id); // id is highlighted as a wrong property
Since first argument might be any object, we should allow passing Record<string,unknown>
which in turn disables our constraint regarding id
being number|string
. This is why I have added IdValidation
utility type. It just checks whether id
property meets condition or not. If it meets - leave id
as is, otherwise - replace id
type with never
. Using never
allows you to highlight only incorrect property which makes it easy to read and understand.
If you are interested in TS validation techniques you can check my articles here and here
CodePudding user response:
The error is telling you all you need. name
doesn't exist on your FnItem
type. You can fix it by adding the property or adding an index signature if you want to add arbitrary keys like so:
interface FnItem {
id?: string | number;
[key: string]: any; // or whatever types you accept
};
As for the generic, I can't tell what you need it for at the moment, as you could simply define the function as
function fn(item: FnItem): void
CodePudding user response:
If you want to pass a generic then you need to tell the function what concrete type implements your interface:
interface FnItem {
id?: string | number;
};
function fn<T extends FnItem>(item: T, callback: (item: T) => void) {
console.log(item.id);
callback(item);
};
fn<myType>({ name: 'Michel', id: 12 }, (item:myType) => { console.log(item.name); });
fn<myType>({ name: 'Michel' }, (item:myType) => { console.log(item.name); });
class myType implements FnItem
{
name: string = "";
id?: number;
}