Reputation: 11500
it seems THIS is related, yet I dont understand.
here is an example of what I want:
myObj
should accept the following settings:
myObj = {'key1': 'val1', 'key2': 'val2', 'key3': 'val3'};
myObj = {'key1': 'val1', 'key2': {'key2': 'val2', 'btnWrap': true}, 'key3': 'val3'};
So I came up with the following type definition:
let myObj: {[key: string]: string | {[key: string]: string, btnWrap: boolean}}
Property 'btnWrap' of type 'boolean' is not assignable to string index type 'string'.ts(2411)
(I dont understand the above error message.)
Please note:
'key2': {'key2': 'val2', 'btnWrap': true}
please note it should be the SAME arbitrary key-name. (key2 for eg)Im happy for some guidance.
@Evert:
let myObj: {
[key: string]: string | boolean,
btnWrap: boolean
}
myObj = {'arr0': 'val0', 'arr1': {'arr1': 'val1', 'btnWrap': false}};
Type '{ 'arr1': string; 'btnWrap': boolean; }' is not assignable to type 'string | boolean'. Type '{ 'arr1': string; 'btnWrap': boolean; }' is not assignable to type 'true'.ts(2322)
Upvotes: 3
Views: 6000
Reputation: 330086
The error message is because the btnWrap
type boolean
doesn't match the string index type string
. The type {[key: string]: string, btnWrap: boolean}}
is trying to say that every property has a string value and that btnWrap
has a boolean value. They can't both be true, so the compiler warns you.
There isn't a single concrete type MyObj
that represents the constraint as you described it. But you can create a generic type which takes the union of key literals K
and produces a type MyObj<K>
:
type MyObj<K extends keyof any> =
{ [P in K]: string | (Record<P, string> & { btnWrap: boolean }) };
The type MyObj<K>
is a mapped type where each property with key P
either has value type string
, or the intersection of {btnWrap: boolean}
with Record<P, string>
. The latter type is itself a mapped type (defined in the standard library) with keys P
and properties string
. So each property of MyObject<K>
must either look like someKey: string
or someKey: {btnWrap: boolean, someKey: string}
.
Again, to describe the type of myObj
, instead of something simple like let myObj: MyObj = ...
, you have to do something like let myObj: MyObj<"key1"|"key2"|"key3"> = ...
where you specify the generic parameters. To prevent you from having to do this yourself, you can use a generic helper function to help infer the type of K
given an object, like this:
const asMyObj = <T extends MyObj<keyof T>>(myObj: T) => myObj;
Now let's try it:
let myObj1 = asMyObj({ key1: 'val1', key2: 'val2', key3: 'val3' });
let myObj2 = asMyObj({ key1: 'val1', key2: { key2: 'val2', btnWrap: true }, key3: 'val3' });
Those work just fine. Now let's see what goes wrong if you violate your constraint:
let badMyObj1 = asMyObj({ key1: 1 });
// error, number is bad
let badMyObj2 = asMyObj({ key1: "val1", key2: { key2: "val2" } });
// error, missing btnWrap
let badMyObj3 = asMyObj({ key1: "val1", key2: { btnWrap: true } });
// error, missing key2 inside value
let badMyObj4 = asMyObj({ key1: "val1", key2: { key3: "val3", btnWrap: true } });
// error, key3 not expected inside value
Those errors are probably what you want to see, right?
Okay, hope that helps. Here's a Playground link to the above code. Good luck!
Upvotes: 7
Reputation: 7304
You type is defined like this:
InnerObj
)This is how to define it:
type InnerObj = {
[key: string]: string | boolean;
btnWrap: boolean;
};
type Obj = {
[key: string]: string | InnerObj;
}
const myObj1: Obj = { key1: 'val1', key2: 'val2', key3: 'val3' };
const myObj2: Obj = { key1: 'val1', key2: { key2: 'val2', btnWrap: true }, key3: 'val3' };
Upvotes: 1