It makes sense that you cannot declare the index type as just boolean in the child class, because it must support at least the same values that the parent supports in order to be a valid subtype. To include boolean as one of the possible values, you can add it to the original signature. Note that the references to itself must also be updated to name the child class in order to support booleans in nested values.
interface Simple {
[key: string]: string | number | Simple | Array<string | number | Simple>;
}
interface SimpleWithBoolean extends Simple {
[key: string]: string | number | boolean | SimpleWithBoolean | Array<string | number | boolean | SimpleWithBoolean>;
}
const simpleWithBoolean: SimpleWithBoolean = {};
simpleWithBoolean.stringProp = "stringProp";
simpleWithBoolean.numberProp = 123;
simpleWithBoolean.booleanProp = true;
simpleWithBoolean.simpleProp = {};
simpleWithBoolean.array = [{nested: "object", booleanProp: true, }, 1, 2, 3, true];
The downside of this solution is that if Simple is updated, SimpleWithBoolean must also be updated to match.
I believe this construction will achieve what you want without needing to keep the two definitions in sync:
interface Simple {
[key: string]: string | number | Simple | Array<string | number | Simple>;
}
type Extend<Original, Union, Addition> = Union extends Array<infer Inner> ? Array<Inner extends Original ? never : Inner | Addition> : Union extends Original ? never : Union | Addition;
type ExtendEach<Original, Addition> = {
[P in keyof Original]: Extend<Original, Original[P], Addition>;
};
type NonRecursiveSimpleWithBoolean = ExtendEach<Simple, boolean>;
type SimpleWithBoolean = ExtendEach<NonRecursiveSimpleWithBoolean, NonRecursiveSimpleWithBoolean>;
const simpleWithBoolean: SimpleWithBoolean = {};
simpleWithBoolean.stringProp = "stringProp";
simpleWithBoolean.numberProp = 123;
simpleWithBoolean.booleanProp = true;
simpleWithBoolean.simpleProp = {};
simpleWithBoolean.array = [{nested: "object", booleanProp: true, }, 1, 2, 3, true];
TS Playground
I'm not totally happy with this, as it requires an intermediate step to create the new type without Simple as a union member and then a final step to make the new type recursive. I don't know if it's possible to refer to the type being created in a mapped type's definition. If anyone knows how to do this in one step without NonRecursiveSimpleWithBoolean, I'd like to know!