Recursive object generation

127 Views Asked by At

I need a type check for any object structure with numerical properties with a previously unknown structure.

type Test = {[key: string]: Test} | number;
// type Test = {[key: string]: Test | number};  // same problem

// This is an example of an object structure. I do not know its true structure in advance.
let test: Test = {a: {b: {c: 2}, d: 2, faild: 'because string'}}; // type checking works good

// type checking NOT works for all of the following examples - BUG ?
// test.obj => any
// Property 'obj' does not exist on type 'Test'.
//   Property 'obj' does not exist on type 'number'.(2339)
test.obj.x.y.z;
test.obj.x.y.z = 2;
test.obj.x.y.faild = 'because string';
test.obj;
test.obj = 2;
test.obj.faild = 'because string';

Playground

1

There are 1 best solutions below

0
On

The error is related to the fact that such a type never defines if the given property has an object or number value, therefore TS complains at the first level that you cannot access property because it can be just a number, and number type has no properties. This is exactly visible in the error:

Property 'obj' does not exist on type 'number'

In every level of this object TS knows only it is object with string keys | number, that is not enough for allowing to access next properties, as in runtime we can deal with number and have runtime exception. And that exactly we want to prevent by using TS, don't we?

In order to tell TS that in the given field there is an object, we need to use generics and simple id function for better type narrowing.

type Test = {[key: string]: Test} | number;
// function which just pass object through but allows for exact type inferring
function makeObj<T extends Test>(arg: T): T {
    return arg;
}

let test = makeObj({a: {b: {c:2}, d: 2, faild: 'because string'}}); // error expected

let test2 = makeObj({a: {b: {c:2}, d: 2, works: 2}}); // success

test2.a.works = 2;
test2.a.b.c = 2;
test2.a.b.c;
test2.a.works = 'string'; // error as expected

In above example TS is inferring type fully, and thanks to that it knows what value can be in the given property. Link to the playground