Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to deduce a type from an object?

I have the following object:

const rights = [{
  label: 'Dashboard',
  value: 'dashboard',
  children: [
    {
      label: 'Statistics',
      value: 'stats',
      children: [
        {
          label: 'Read',
          value: 'read',
        },
        {
          label: 'Write',
          value: 'write',
        },
      ],
    },
    {
      label: 'CSV Export',
      value: 'csvExport',
      children: [
        {
          label: 'Read',
          value: 'read',
        },
        {
          label: 'Write',
          value: 'write',
        },
      ],
    },
  ],
}] as const;

From this object, I want to derive the following type:

{
  dashboard: {
    statistics: {
      read: boolean,
      write: boolean,
    },
    csvExport: {
      read: boolean,
      write: boolean,
    },
  }
}

However, it seems that recursion in TypeScript is limited. I have tried many different approaches but cannot figure out how to achieve this.

How can I recursively generate a type from this nested structure?

Edit : it is the best I've got but I'm still far from what I want :


type ObjectType = typeof object;

type CurrentNode = {
  readonly label: string;
  readonly value: string;
  readonly children?: CurrentNode[];
};

type TransformToBoolean2<T extends CurrentNode[]> = {
  [K in T[number]['value']]: T[number]['children'] extends CurrentNode[]
    ? TransformToBoolean2<T[number]['children']>
    : boolean;
};

type booleanObject = TransformToBoolean2<ObjectType>;

The type of booleanObject is the following :

type booleanObject = {
    a: boolean;
} 

And more than this, my linter informs me that ObjectType doesn't satisfy 'CurrentNode[]'

like image 963
RandomDude1234 Avatar asked Feb 01 '26 07:02

RandomDude1234


1 Answers

Here's one way to do it:

type TransformToBoolean<T extends readonly CurrentNode[]> = {
  // Map over the array/tuple, translating it into an object type using the
  // `value` property in each element as the key.
  [I in keyof T & number as T[I]['value']]:
    // If the element has children, recursively transform them, otherwise
    // use `boolean` as the type.
    T[I]['children'] extends readonly CurrentNode[]
      ? TransformToBoolean<T[I]['children']>
      : boolean;
};

(Playground)


Explanation

The whole thing is is a mapped type which maps over the elements of T (a tuple of CurrentNodes).

I in keyof T & number

This says we only want to consider the numeric keys of the tuple; i.e. its elements' indexes, not other properties like length1.

as T[I]['value']

This remaps the keys to use the value of the value property as the key in the output type (so we get read: boolean rather than 0: boolean).

Output property values are computed via a conditional type.

T[I]['children'] extends readonly CurrentNode[]

The children property is optional. This condition checks whether it is defined on the current node2.

? TransformToBoolean<T[I]['children']>
: boolean

If so, we recursively apply TransformToBoolean to the children. Otherwise, the property value is boolean.


1 Often mapped types ignore the non-element properties of array types, but this is actually special behavior that only kicks in under certain circumstances. Key remapping disables this behavior (e.g. observe the difference between NotRemapped and Remapped in this example) and a simple I in keyof T wouldn't work because it attempts to map over all properties (including length, toString, map, etc, whose values aren't CurrentNodes).

2 This could be written with no change in the results as T[I]['children'] extends {} or even T[I]['children'] extends {} | null. Since T[I] is a CurrentNode and CurrentNode['children'] is either absent, undefined, or readonly CurrentNode[], all we need to do is check that it has a value other than undefined. I thought writing out its specific type would make things easier to understand, though.

like image 193
Matt Kantor Avatar answered Feb 03 '26 21:02

Matt Kantor



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!