Demo: https://tsplay.dev/mMM8Vm
I got an array of T
and that has keys of the type string
. So I want to iterate over all entries in the array and trim the string.
Error:
Property 'trim' does not exist on type 'T[Extract<keyof T, string>]'
ifAlreadyExists<T>(
data: Array<T>,
key: Extract<keyof T, string>,
): boolean {
return (
data.filter(
(item) => {
const v = item[key];
return v.trim().toLowerCase()
}
).length === 0)
}
How can I tell Typescript that item[key]
will be of the type string?
CodePudding user response:
As kaya3 mentioned, your signature constrains key
to string keys, rather than to keys that have string values. To do the latter, you can introduce a type variable Key
(which can be any key type), and constrain the array elements to objects that have a string value for the key denoted by Key
(i.e. {[K in Key]: string}
):
ifAlreadyExists<Key extends PropertyKey>(
data: Array<{[K in Key]: string}>,
key: Key,
): boolean {
..
}
CodePudding user response:
If you want to inspect a given collection if a given property of any item is holding a specific value maybe this might do the trick
type Book = {
id: string;
title: string;
};
class AppComponent {
constructor() {
const bookList: Array<Book> = [{ id: '111', title: 'Title 1' }, { id: '222', title: 'Title 2' }];
this.propertyValueExists(bookList, 'title', 'Title 2');
}
propertyValueExists<Key extends PropertyKey>(
collection: Array<{[K in Key]: string}>,
propertyKey: Key,
expectedValue: string
): boolean {
return collection.some(item => {
const actualValue = item[propertyKey];
return actualValue.trim().toLowerCase() === expectedValue;
});
}
}
new AppComponent();
Example:
CodePudding user response:
Ignoring the boolean vs string return value in the filter and deliberately not constraining T
to be a Record<PropertyKey, string>
.
Use a type-guard:
return (
data.filter(
(item) => {
const v = item[key];
if (typeof v === 'string') {
return v.trim().toLowerCase();
}
return 'something?';
}
).length === 0)
For extra points you may want to only expose keys for which the implementation is valid.
Only
produces a type where for each key of T
its corresponding field is assignable to U
.
type Only<T, U> = {
[K in keyof T]: T[K] extends U ? K : never
}[keyof T];
then
ifAlreadyExists<T>(
data: Array<T>,
key: Only<T, string>, // only keys where values are string-like
): boolean {
...
}
And there are values other than strings in the Book shape.
type Book = {
id: string;
title: string;
isbn: number;
genre: 'fantasy' | 'horror'
};
const bookList: Array<Book> = [{ id: '222', title: 'Title 1', isbn: 1237681231, genre: 'fantasy' }];
this.ifAlreadyExists(bookList, "id"); // fine
this.ifAlreadyExists(bookList, "title"); // fine
this.ifAlreadyExists(bookList, "genre"); // fine
this.ifAlreadyExists(bookList, "isbn"); // Error! isbn is not string