Andre Elrico
Andre Elrico

Reputation: 11500

arbitrary type definition gives: Property 'x' of type 'boolean' is not assignable to string index type 'string'

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:

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

Answers (2)

jcalz
jcalz

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

gilamran
gilamran

Reputation: 7304

You type is defined like this:

  • All the properties should be a string or some inner type (InnerObj)
  • The InnerObj properties should be a string or a boolean

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

Related Questions