How to type keys (Keys) of objects nested in Typescript?

Asked

Viewed 115 times

4

I need that guy ColumnDefinitionType accept the object keys IUser, as well as accept the keys of objects nested to the IUser.

export interface IUser {
  userId: number;
  username: string;
  name: string;
  firstAccess: number;

  status: number;
  profile: {
    profileId: number;

    role: string;
  };
}


export type ColumnDefinitionType<T, K extends keyof T> = {
  key: K;
  header: string;
  width?: number;
};



const columns: ColumnDefinitionType<IUser, keyof IUser>[] = [
  {
    key: 'name',
    header: 'Nome',
    width: 150
  }
]

In this case, the constant columns accepts the key name on the property key, but I can’t get the key to her role existing in user.profile.role, so I’d like to know how to extend my typing ColumnDefinitionType.

  • This is because role is not a key to IUser. How would the key? "profile.role"?

  • Exactly Luiz, he does not accept because it is not Iuser key, so I need to know if there is any way to adjust my Columndefinitiontype to accept the keys of nested objects. Either by going directly 'role' or as you mentioned 'profile.scroll'.

1 answer

5


I think it is not possible to do this with arrays (at least not in a way simple) because the compiler has no way of knowing the type of the element associated with a specific key for each element of the array. He would form a union with all possible types.

But it’s possible to make a structure out of objects. Something like this:

export interface IUser {
  userId: number;
  profile: {
    profileId: number;
  };
}

const columns: ColumnDefinitionType<IUser> = {
  userId: {
    header: 'ID do usuário',
    width: 50
  },
  profile: {
    subColumns: {
      profileId: {
        header: 'ID do perfil',
        width: 50
      }
    }
  }
};

Note that I have removed some fields to simplify the example.

It is a "recursive" structure. And precisely because of this, we need to create a recursive type for the case of objects nested to the main object. For this, you can use conditional types.

Behold:

export type BareColumn = {
  header: string;
  width: number;
};

export type ColumnDefinitionType<T> = {
  [K in keyof T]: T[K] extends object
    ? { subColumns: ColumnDefinitionType<T[K]> } // Note que este tipo é recursivo para objetos.
    : BareColumn;
};

Basically, you have a Mapped type that maps each property of the object T for a new type. This new type is determined through a conditional type, so that:

  • If the type of the current element is object (any primitive value), use an object that calls again the type ColumnDefinitionType, passing the current object (which creates a recursive "structure").
  • Otherwise, it is inferred that it is a primitive type (no object) and use the type BareColumn (which contains simple information for a column without "children").

Thus, instead of having an array with information from each column, one has an object, in which the keys represent one’s own key (that before was a property of each element of the array).

I don’t think it will be such a big limitation, but that is my assumption. Maybe this will serve as a way.

I left the complete example on Typescript playground.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.