-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Quick fix for 'unions can't be used in index signatures, use a mapped object type instead' #24220
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
Comments
You can do this: type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any}; Though Edit: Though if you could make the caveat a non-issue, I'd be eternally grateful! |
i'd like to work on this 😆 |
what should we do if containing object type is an class? so what should follow code do after quickfix? type K = "1" | "2"
class SomeType {
a = 1;
[prop: K]: any;
} |
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? |
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:
I tried below, all failed:
|
@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 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. |
Use 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: :) |
its even better when using Partial
|
@DanielRosenwasser |
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: 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, 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 |
This will not yield error in this example:
I thought that's the whole point |
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 😮 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: #14094 It is still technically possible though...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.
|
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. |
Thanks! |
"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' |
Very useful. Thanks! 🚀 |
The following code:
Gives this error message:
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 anyextends
clausesThe text was updated successfully, but these errors were encountered: