2

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:

  • key1, 2, 3 stand for ARBITRARY key-names.
  • in '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)

Andre Elrico
  • 10,956
  • 6
  • 50
  • 69
  • You are saying that key1, 2 3 stand for ARBITRARY key-names, but then you want the inner object declaration to include this arbitrary key? Can you give a real example? – gilamran Mar 19 '19 at 15:47
  • I have two examples gilamran. See also my edit. Is anything still unclear? – Andre Elrico Mar 19 '19 at 15:49

2 Answers2

5

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!

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • thank you, you seem to have a PHD in typing :D. Well thats well above me. I think I'll use @gilamran approach because the checking is not thaaaat crucial, but your answer is definitaly some gold!! – Andre Elrico Mar 19 '19 at 16:05
  • I was just about to edit my answer and use `Record`... Cool answer @jcalz! – gilamran Mar 19 '19 at 16:13
1

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' };
gilamran
  • 7,090
  • 4
  • 31
  • 50
  • 1
    thank you. Your solution is the best i got so far, but: `const myObj1: Obj = {'arr0': 'true', 'arr1': {'otherKeyNameThanArr1': 'val1', 'btnWrap': false}};` should not work. – Andre Elrico Mar 19 '19 at 15:54
  • I'm not sure that it's possible, I'm investigating it – gilamran Mar 19 '19 at 15:56