The following code:
type K = "foo" | "bar";
interface SomeType {
[prop: K]: any;
}
Gives this error message:
An index signature parameter type cannot be a union type. Consider using a mapped object type instead.
Nobody knows what mapped object types are, so let's give them a quick fix that
extends
clauses if the containing object type is an interface and has any extends
clausesNobody knows what mapped object types are, so let's give them a quick fix that
+1, just came here because I was expecting 2.9 to support unions as index signatures per your example code. I think this has been a long desired feature: #5683, #16760, etc..
You can do this:
type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};
Though Bar
has no index signature (i.e., you can't then do (obj as Bar)[value as Foo]
).
Edit: Though if you could make the caveat a non-issue, I'd be eternally grateful!
i'd like to work on this :laughing:
Moves other members to a separate object type that gets combined with an intersection type
what should we do if containing object type is an class?
I can only imagine that it is an interface
so what should follow code do after quickfix?
type K = "1" | "2"
class SomeType {
a = 1;
[prop: K]: any;
}
so what should follow code do after quickfix?
I would say this should not be fixable..
@mhegazy I'm using 3.0.0-rc and still getting the same error as originally posted. Is this expected?
I'm using 3.0.0-rc and still getting the same error as originally posted. Is this expected?
yes. the error is correct. this issue was tracking adding a quick fix for it, that is the light pulp next to the error message.
no code actions available with 2.9.1 and vscode
@ThaJay We won't backport this feature, try setting up a newer version.
Obviously. I'm sorry for not checking the timeline first, just assumed it would be new enough. New to ts. Will check with version 3.
how to describe this:
function createRequestTypes(base){
return ['REQUEST', 'SUCCESS', 'FAILURE'].reduce((acc, type) => {
acc[type] = `${base}_${type}`
return acc
}, {})
}
const user = createRequestTypes('USER')
console.log(user.REQUEST) // error
// just string? like:
interface IRequestType: {[key: string]: string}
I tried below, all failed:
type requestStatus = 'REQUEST' | 'SUCCESS' | 'FAILURE'
type requestTypes = {
[key in requestStatus]: string
}
// or
interface IRequestTypes {[key: keyType]: string}
// or even
type requestTypes = {
FAILURE: string,
SUCCESS: string,
REQUEST: string
}
@maicWorkGithub here you go:
const user = createRequestTypes('USER')
console.log(user.REQUEST)
function createRequestTypes(base:string):requestTypes {
const result : requestTypes = {}
const arr : requestStatus[] = ['REQUEST', 'SUCCESS', 'FAILURE']
return arr.reduce((acc, type) => {
acc[type] = `${base}_${type}`
return acc
}, result)
}
type requestStatus = 'REQUEST' | 'SUCCESS' | 'FAILURE'
type requestTypes = { [key in requestStatus]?: string }
@ihorskyi Thanks!!
Just curious why type
works, but interface
doesn't. Can someone explain, please? What's the reason for such a limitation (or a feature?) of interface
.
type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any}; // ok
interface Baz {[key in Foo]: any} // =>
// A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1169)
// A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)
// 'Foo' only refers to a type, but is being used as a value here.ts(2693)
This was an amazing auto-fix to discover. Thank you for implementing it! :)
Same for classes.
You can do this:
type Foo = 'a' | 'b'; type Bar = {[key in Foo]: any};
Though
Bar
has no index signature (i.e., you can't then do(obj as Bar)[value as Foo]
).Edit: Though if you could make the caveat a non-issue, I'd be eternally grateful!
Use Record
instead!
type Foo = 'a' | 'b'
type Bar = Record<Foo, any>
To add one more example of this using a class...
class Foo {
a: string;
b: string;
}
type Bar = {[key in keyof Foo]: any};
No words, just meme:
https://media1.tenor.com/images/23d9d746fc87b3a93298af43dae21f6a/tenor.gif
:)
its even better when using Partial
type A = 'x' | 'y' | 'z';
type M = Partial<{
[key in A]: boolean
}>;
Nobody knows what mapped object types are, so let's give them a quick fix that
@DanielRosenwasser
Why can't the error message suggest the answer e.g. show a quick example using mapped type - that would only be couple of lines of code which will be consistent with the average length of Typescript error messages :trollface:
does anyone know if is possible to say that the interface which uses the type or enum as key can accept only one property?
For example a signature like this:{ <field>: { <and|or|xor>: <int> } }
took from mongo bitwise operator.
export enum BitwiseOperator {
and = "and",
or = "or",
xor = "xor",
}
export type BitwiseCondition = {
[key in BitwiseOperator]?: number;
}
An then when using it, I would like to validate that the variable which is defined by the interface, has only one property.
const query: BitwiseCondition = {
and: 5,
or: 6 // raise a ts error
};
@b4dnewz You can't do it in Typescript. Workaround: https://github.com/Microsoft/TypeScript/issues/10575
@b4dnewz, if you only want 1 property, why not do it like this?
export enum BitwiseOperator {
and = "and",
or = "or",
xor = "xor",
}
export type BitwiseCondition = {
operator: BitwiseOperator;
value: number;
}
@benwinding unfortunately the returned shape is different from what mongodb expects
@apieceofbart thanks for the suggestion, I've looked into it, a bit redundant in terms of interfaces but can work, I'm not sure if I'll implement it now, since it's not a big deal if the final user tries a bitwise condition with two operators, mongo will throw an error anyway
I'm trying to keep the mongo-operators definitions as simple as possible to avoid me headaches 馃榿 maybe in future a proper support is added
@b4dnewz fair enough,
Perhaps a simpler option you might be able to use is:
export type BitwiseCondition =
| { or: number }
| { xor: number }
| { and: number }
That's about the closest you'll get without too much duplication
@b4dnewz fair enough,
Perhaps a simpler option you might be able to use is:
export type BitwiseCondition = | { or: number } | { xor: number } | { and: number }
That's about the closest you'll get without too much duplication
This will not yield error in this example:
const query: BitwiseCondition = {
and: 5,
or: 6 // raise a ts error
};
I thought that's the whole point
@apieceofbart,
This will not yield error in this example:
export type BitwiseCondition =
| { or: number }
| { xor: number }
| { and: number }
const query: BitwiseCondition = {
and: 5,
or: 6 // doesn't raise a ts error!
};
Woah! that's weird :open_mouth: I did not know that!
It's seems that Typescript doesn't support mutually exclusive types for objects. It's also was proposal for the language here: https://github.com/microsoft/TypeScript/issues/14094
From this stackoverflow answer this is possible to achieve this using conditional types (the hardest types), but it aint pretty....
/*
XOR boiler plate
*/
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: T | U;
type XOR3<S, T, U> = XOR<S, XOR<T, U>>;
// Code start
export type BitwiseCondition = XOR3<
{ or: number },
{ xor: number },
{ and: number }
>;
const query1: BitwiseCondition = {
and: 5
};
const query: BitwiseCondition = {
and: 5,
or: 6 // raise a ts error
};
If anyone could make this prettier or better, please do
@mvasin FWIW, this _appears_ to achieve the same result, but I agree entirely that it should be a feature of interfaces just as it is on types.
type Foo = 'a' | 'b';
type Bar = {
[key in Foo]: any
}
interface A extends Bar { }
class Wol implements A{
a: any;
b: any;
}
For typescript 3.5, it seems like I have to do this:
export interface DataTableState {
columnStats: {[key in keyof DataTable]?:{}}
}
Is this the best way to do this?
Why exactly can't an index signature use an enum type? The mapped type almost does what I want, but then TypeScript expects every string from the enum to exist as a defined key. I don't actually want to assert that every key exists, more that if any keys do exist, they must live in the enum.
For example for the type:
type MyType = {
[Key: 'foo' | 'bar' | 'zip']: number;
};
This should satisfy:
const x: MyType = {
foo: 1,
zip: 2
};
While I could just set the other keys undefined for a mapped type, I prefer to make the keys optional, but if they're present, the value cannot be undefined. If I make the mapped type values optional the code works but the types are less strong.
its even better when using Partial
type A = 'x' | 'y' | 'z'; type M = Partial<{ [key in A]: boolean }>;
Thanks!
Useful when you need to define a type that partially matches a dictionary
"Partial" can be used on Records too:
type Foo = 'a' | 'b';
let foo1: Record<Foo, number> = { a: 1, b: 2 };
let foo2: Partial<Record<Foo, number>> = { a: 1 };
I find myself unwittingly visiting this GitHub page every month or so.
My latest one is a real simple one:
interface ObjectLiteral {
[key: string | number]: any
}
export const mapToObjectLiteral = (map: Map<string|number, any>) =>
Array.from(map).reduce((objLit, [key, value]) => {
objLit[key] = value
return objLit
}, {} as ObjectLiteral)
I can scroll up and figure out a workaround, but just wanted to provide feedback that this issue happens frequently in day to day work in slightly different scenarios.
here is an example:
type MapKey = string | number;
type ObjectLiteral<T extends MapKey, V = any> = {
[P in T extends number ? string : T]: V;
};
export const mapToObjectLiteral = <T extends MapKey, V>(map: Map<T, V>) =>
Array.from(map).reduce((objLit, [key, value]) => {
objLit[key as keyof ObjectLiteral<T>] = value;
return objLit;
}, {} as ObjectLiteral<T, V>);
// how to make a better type of map ?
const m = new Map<1 | "foo", "a" | "b">();
m.set(1, "a");
m.set("foo", "b");
const o = mapToObjectLiteral(new Map(m));
console.log(o[1], o.foo); // just got an union type of every member of 'o'
https://github.com/microsoft/TypeScript/issues/24220#issuecomment-504285702
To add one more example of this using a class...
class Foo { a: string; b: string; } type Bar = {[key in keyof Foo]: any};
Very useful. Thanks! 馃殌
Most helpful comment
You can do this:
Though
Bar
has no index signature (i.e., you can't then do(obj as Bar)[value as Foo]
).Edit: Though if you could make the caveat a non-issue, I'd be eternally grateful!