Typing dynamic object fields

Viewed 52

Let's say I have dynamic object which has a union type:

  foo: {[key in 'num' | 'str' | 'obj']: number | string | object};

Now i assign object properties as follow:

  foo.num = 1;
  foo.str = 'text';
  foo.obj = {};

And it works. However when I try to access:

  foo.num

And use this value f.e. to reduce it by summing up all foo.num properties - I can't because compiler tells me that foo.num is type of union. How to tell compiler that foo.num is type of number? I know there is a way with as but it is not elegant way. I've been looking for answer in SO, Medium and other programming sites but couldn't find solution.

Edit:

It was just an example. My real case is like this. I have an array of objects which I have to group by one of their property which is Enum type and then map them. So I am doing something like this:

Object.values(Enum).forEach(
    enum => subjects[enum].next(
        array
            .filter(object => object.type === enum)
            .map(object => object.payload)
    )
);

I though best way would be create object which will store all of subjects but this make all subjects inside of this object of union type. If enum values change for some reason, I will need to change only enum type, nothing more.

1 Answers

Since you have only 3 keys, doesn't it make sense to create an interface which explicitly declares the type of each key?

interface Foo {
    num: number;
    str: string;
    obj: object;
}

const foo: Foo = {
    num: 1,
    str: 'text',
    obj: {},
}

console.log(foo.num); // (property) Foo.num: number
console.log(foo.str); // (property) Foo.str: string
console.log(foo.obj); // (property) Foo.obj: object

See on TypeScript playground.


Based on the code you've provided in the comments, you have an enum that is being used as a key. TypeScript will still need some manual help with assigning the correct type to the key:

enum NotificationType {
  FRIEND_REQUEST = "FRIEND_REQUEST",
  NEW_MESSAGE = "NEW_MESSAGE",
  GROUP_CREATED = "GROUP_CREATED",
  GROUP_DELETED = "GROUP_DELETED"
}

interface Group {
  name: string;
  users: object[];
}

interface FriendRequest {
  user: object;
}

interface Message {
  messageContent: string;
}

interface Foo {
    [NotificationType.FRIEND_REQUEST]: FriendRequest[],
    [NotificationType.NEW_MESSAGE]: Message[],
    [NotificationType.GROUP_CREATED]: Group[],
    [NotificationType.GROUP_DELETED]: Group[]
}

const foo: Foo = {
    [NotificationType.FRIEND_REQUEST]: [],
    [NotificationType.NEW_MESSAGE]: [],
    [NotificationType.GROUP_CREATED]: [],
    [NotificationType.GROUP_DELETED]: []
}

console.log(foo[NotificationType.FRIEND_REQUEST]); // FriendRequest[]
console.log(foo[NotificationType.NEW_MESSAGE]);    // Message[]
console.log(foo[NotificationType.GROUP_CREATED]);  // Group[]
console.log(foo[NotificationType.GROUP_DELETED]);  // Group[]

See example on TypeScript Playground.

Related