-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
keyof for arrays #20965
Comments
You can get |
Thanks @DanielRosenwasser, #10195 would be helpful but ideally we would be able to do without indexed access types. What I was looking for was a way to express: "All keys in an object must be a value in an array". |
You could always define a type alias for that, but you won't be able to get around using export type ValuesOf<T extends any[]>= T[number];
declare var x: ...;
type Foo = ValuesOf<typeof x>; |
That's interesting, I didn't think of that. I'll close out the ticket. Thanks! |
@bezreyhan I came across this issue while searching around for something else. I don't know if this will be useful to you two months later but I think this will do essentially what you want as long as you don't need to save the array in a variable beforehand: function functionGenerator<T extends string, U = { [K in T]?: string }> (keys: T[]): (p: U) => U {
return (p: U) => p
}
const testFun = functionGenerator(['one', 'two'])
testFun({one: '1'}) // no error
testFun({two: '2'}) // no error
testFun({three: '3'}) // error as expected because 'three' is not a known property If you do need the array in a variable, it looks like this will work: function literalArray<T extends string>(array: T[]): T[] {
return array
}
const arr = literalArray(['one', 'two'])
const testFun2 = functionGenerator(arr)
testFun2({one: '1'}) // no error
testFun2({two: '2'}) // no error
testFun2({ three: '3' }) // error as expected because 'three' is not a known property Here is a link of those examples in the playground: Link |
@kpdonn Thanks. But I have problem with using array variable instead of direct array in parameter const functionGenerator = <T extends string, U = { [K in T]?: string }>(keys: T[]): U => {
return keys.reduce((oldType: any, type) => ({ ...oldType, [type]: type }), {})
}
const data = ['one', 'two']
const testFun = functionGenerator(data)
testFun.one
testFun.three <<<<<< also ok, expected throw error |
Using resolveJsonModule, would it be possible to get the type of a JSON array without explicitly declaring the type of the JSON module and losing typechecking on its data? I tried the ValuesOf approach but it seems like I would have to declare the type of my function argument explicitly. |
@kpdonn I find your solution very interesting. function functionGenerator<T extends {name: string}, U = { [K in T['name']]?: string }>(keys: T[]): (p: U) => U {
return p => p;
}
const testFun = functionGenerator([{name: 'one'}, {name: 'two'}]);
testFun({ one: '1', two: '2' }); // no error
testFun({ two: '2' }); // no error
testFun({ three: '3' }); // error expected but type diagnostic passes |
If anyone is interested, I found the solution of my problem: function functionGenerator<
V extends string,
T extends {name: V},
U = { [K in T['name']]?: string }
>(keys: T[]): (p: U) => U {
return p => p;
}
const testFun = functionGenerator([{name: 'one'}, {name: 'two'}]);
testFun({ one: '1', two: '2' }); // no error
testFun({ two: '2' }); // no error
testFun({ three: '3' }); // throws an error |
Is there anything wrong with doing the following:
When I do that, The above syntax is kind of weird, but it seems to do the job? |
Typescript 3.4 made things better:
👍 |
@benneq Thanks for that hint. I'm now able to type function's parameter without having to create an const pages = <const> [
{
label: 'homepage',
url: ''
},
{
label: 'team',
url: ''
}
];
// resulting signature = function getUrl(label: "homepage" | "team"): void
function getUrl(label: (typeof pages[number])['label']) {}
getUrl('homepage') // ok
getUrl('team') // ok
getUrl('bad') // wrong |
Is there a way to do what @Serrulien posted, but also with types defined for each object in the array? Something like: type Page = {
label: string;
url: string;
}; Then enforce that type of each object of the |
You need to decide whether
|
@fabb, right, but I wanted to combine that with something like this to have a type with all the valid labels: type Labels = (typeof pages[number])['label']; Without the |
I fear you have to choose between your literal defining the type, or explicit typing without |
I am trying to use type Events = [
'repo:push',
'pullrequest:unapproved',
'pullrequest:created'
]
export interface InterosTag {
[key: string]: {
[key: keyof Events]: { // but this does not work
"jenkins-job": string,
"deploy": boolean,
"eks-key": string
}
}
} any help appreciated - not sure if related or not: #32489 |
@ORESoftware look at @benneq‘s answer above. |
@fabb i tried earlier today but it didnt work, how would I use it in the context of my code above? |
@ORESoftware do it like this:
|
@fabb ok that worked, I missed the I fix that, I tried using: type Events = typeof events[number];
[key in Partial<Events>]: {
'jenkins-job': string
'deploy': boolean
'eks-key': string
} but I filed a related ticket: #32499 |
@ORESoftware you are holding it wrong ;-)
|
noob here, based on the suggestions here Im trying to write a helper function to check if an object has the required keys const requiredKeys = <const>[
'key1',
'key2',
'key3'
];
function hasKeys(
unknownObject: { [key: string]: any },
requiredKeys: readonly string[]
): unknownObject is { [key in typeof requiredKeys[number]]: unknown } {
return Object.keys(requiredKeys).every(
required => unknownObject[required] !== undefined
);
} this works but unknownObject ends up being typed as |
@ashleymcveigh This should do it for arrays: function includes<T, U extends T>(arr: readonly U[], elem: T): elem is U {
return arr.includes(elem as U); // dirty hack, i know
} Though I'm not sure if you can use |
@ashleymcveigh you are no noob if you write code like this. It's curious, if you extract
|
So I was playing around and I figured it out. function objectHasKeys<T extends string>(
unknownObject: { [key: string]: unknown },
requiredKeys: readonly T[]
): unknownObject is { [Key in T]: unknown } {
return requiredKeys.every(
required => unknownObject[required] !== undefined
);
}
function test(thing: { [key: string]: unknown }) {
const required = <const>['A', 'B']
if (!objectHasKeys(thing, required)) return
thing // <-- this is typed as { A: unknown, B: unknown }
} |
Is there a way to get a literal type from an iterative operation? function getValues<T>(object: T, keys: (keyof T)[]) {
return keys.map((key) => object[key]);
}
const result = getValues({ "1": 1, "2": 2, "3": "three" }, ["1", "3"]);
typeof result; // (string | number)[] I want the literal type of the result. typeof result; // [1, "three"] I'd also like to create a generic type to represent this function type GetValues<T, K extends readonly (keyof T)[]> = {
(object: T, keys: K): T[K[number]][];
};
// produces (1 | "three")[], instead of [1, "three"]
type Values = GetValues<{ "1": 1, "2": 2, "3": "three" }, ["1", "3"]>; |
This is the release note with the relevant info about the solution (which @benneq mentioned) |
Using function getValues<T, K extends readonly (keyof T)[]>(
object: T,
keys: K
): T[K[number]][] {
return keys.map(key => object[key]);
}
const result = getValues(
{ "1": 1, "2": 2, "3": "three" } as const,
["1", "3"] as const
);
typeof result; // produces (1 | "three")[], instead of [1, "three"] I'm trying to get a literal type from an operation on a literal type. I think the issue is |
@daniel-nagy All together this nearly works as expected:
While the result type is now correctly Here's a small workaround that fixes this error 🎉:
|
in the latest ts "version": "3.9.6" export const extraKeys = ['app', 'title', 'package', 'deeplink', 'url', 'logo', 'image', 'type'] as const;
export type Extra ={
[key in typeof extraKeys[number]]: string;
}; |
For a value, I found that I can use: const keys = ['app', 'title', 'package', 'deeplink', 'url', 'logo', 'image', 'type'] as const;
type Key = typeof keys[0] |
For what I am doing, something like this is sufficient: type Prop<AcceptableKeys extends Array<string>> = {
[key in AcceptableKeys[number]]: string;
};
function test(prop: Prop<["one", "two"]>) {
return prop;
}
test({ three: "3" }); // throws an error I specifically need this functionality for generating specific type Request<
PathParameters extends Array<string>,
QueryParameters extends Array<string>,
Body extends Record<string, unknown>
> = {
pathParameters: {
[key in PathParameters[number]]: string;
},
queryStringParameters: {
[key in QueryParameters[number]]: string;
};
body: Body;
};
type TestBody = {
c: string;
};
type TestRequest = Request<["a"], ["b"], TestBody>;
const testOne: TestRequest = {
pathParameters: { a: "foo" },
queryStringParameters: { b: "bar" },
body: { c: "baz" },
};
// No error
const testTwo: TestRequest = {
pathParameters: { a: "foo" },
queryStringParameters: { bee: "bar" }, // Throws an error
body: { c: "baz" },
}; |
For simple array, I do this (add "readonly type") export type ValuesOf<T extends readonly any[]>= T[number];
export const ALL_BASIC_FUNCTIONS_NAMES = ["sub", "add", "div", "mul", "mod"] as const
const keys = ValuesOf<typeof ALL_BASIC_FUNCTIONS_NAMES> |
I use two approaches: // Option 0: Define type using object
const someTypeExample = {
a: 1,
b: 'str',
c: 'foo' as 'foo'|'bar'
};
type SomeType0 = typeof someTypeExample;
// Option 1: If object stores different types (with disadvantages)
type SomeType = {
a: number;
b: string;
}
const typeVar: SomeType = {
a: 10,
b: 'string'
}
// used for typechecking
type SomeTypeKey = keyof SomeType;
// create an array to be used in runtime
// disadvantage is that properties need to be repeated
const keys: SomeTypeKey[] = ['a', 'b']; // type checked
// TODO what I'm missing is:
// const keys = keys<SomeTypeKey>(); that would return ['a', 'b'] during transpiling
// ie. native support of https://github.com/kimamula/ts-transformer-keys
// which is out of scope for TS: https://github.com/microsoft/TypeScript/issues/13267
let isValidKey = keys.includes('c' as SomeTypeKey)
// Option 2: start from keys array definition, in case all values have the same type
const myKeys = ['foo', 'bar'] as const; // as const does the magic
type MyKey = typeof myKeys[number]; // = 'foo' | 'bar'
type MyMap = Record<MyKey, string>;
type MyMap2 = { [key in MyKey]: string }; |
I just managed to make my function return a type checkable object: const indexKeys = <K extends string>(keys: readonly K[]) => {
type Result = Record<K, number>;
const result: Result = {} as Result;
const {length} = keys;
for (let i = 0; i < length; i++) {
const k = keys[i];
result[k] = i;
}
return result;
}; Here the type checker will complain: // Property 'zz' does not exist on type 'Result'.
const {aa, zz} = indexKeys(['aa', 'bb']); |
added strict types fixes #291 with help and reference of microsoft/TypeScript#20965 (comment)
As mentioned above by @7kms this works to change an array into a type const arr= ['foo', 'bar'] as const;
const myVar:typeof arr[number] = "foo"
// Hovering over myVar shows:
// const myVar: "foo" | "bar" |
TypeScript Version: 2.5.3
Would it be possible to have something like the
keyof
operator for arrays? The operator would be able to access the values in the array.Code
With object we can do the following:
Could we do something like the following with arrays?
The text was updated successfully, but these errors were encountered: