在TypeScript中键入对象数组

时间:2019-09-15 10:10:38

标签: javascript typescript

使用打字稿键入对象数组:

以下是接口:

export interface IUser {
  id: string;
  order: number;
}

export interface IUsersLogin {
  result: number;
  UserId: string;
}

export interface IUsersUsers {
  id: number;
  order: number;
}

export interface IOriginalData {
  Users?: IUser[];
  UsersLogins?: IUsersLogin[];
  UsersUsers?: IUsersUsers[];
}

在这里,我使用以下接口创建对象:

const originalData: IOriginalData = {
  Users: [
    {
      id: "e4e2bb46-c210-4a47-9e84-f45c789fcec1",
      order: 1
    },
    {
      id: "b95274c9-3d26-4ce3-98b2-77dce5bd7aae",
      order: 2
    }
  ],
  UsersLogins: [
    {
      result: 1,
      UserId: "e4e2bb46-c210-4a47-9e84-f45c789fcec1"
    },
    {
      result: 0,
      UserId: "b95274c9-3d26-4ce3-98b2-77dce5bd7aae"
    }
  ],
  UsersUsers: [
    {
      id: 1,
      order: 0
    },
    {
      id: 2,
      order: 0
    }
  ]
};

在这里,我操纵该对象的数据以将其推入另一个对象:

interface IPushedDataItem {
  data: IUser | IUsersLogin | IUsersUsers;
}

type TypePushedData = Array<IPushedDataItem>;

let pushedData: TypePushedData = [];
Object.keys(originalData).forEach(item => {
  pushedData.push({
    data: originalData[item]
  });
});

在此过程中,我无法正确键入pushedData,并且它抱怨data:originalData[item]

您可以在TypeScript玩法中找到它:

http://www.typescriptlang.org/play/#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgSQFUBnNOAbwCg5EATALjmJimQHMBuauaWtRpAFcAtgCM0XAL6VKoSLEQo0WXARJpiAGQhtkFblGDFBAGxgCR4qFxrqo+BkxbspMudHjJUGHHiKkoYjtifRoERyExCW5efjhIq1dZcA9FbxU-AHlWXSRMEwARTBhMULhggH5GfzQAbQBdG3KArR1kYiq1Fu1chqbgyuqBlr7KaUpsCCRmHhzkfKKS6uyEXIXi0oBeMuDGWu4aKhpjukYAImAAFmAAJlFRS4A2AFpsG4BGAAZny8xLgHZngBOYAADkuz3QlwArNh-qCgehcNh3mcADQHY6xKCMd6YyQYk5HE6nOBnURA6E3f6XbBA54AZloNxetOADOBoNEN2e-3+tFw0NEtH+mEwwHRmJo2MYN3x3HqhOaGh67T2mOJJ0MxjMuKVJzsDnOV1u9yerw+31+AOBYIhUNh8MRyNR+P1ZS1RlM5jgn3dtgCRrJFKpNLpjOZrNwHKBXJ5fIFwCFIrFEvlNEV3GGGnVRKlpPe-tmfBxvrdGvz4VlRZlZZO0gzYy4lC8yl8BAACoJiAALYC0RaYfCoYRlWgbIYBOAAHy6KraSBnc8CwSSMAAnmA8AAVTfALu9-uDuDbACCUCgmHXAB58Ae+wONsPgMIAHzNkzAeBgbsPweMXct3vI8NhPOBRkyUQACtgGwGAADoAGtgHXYgAApoFWeZCg2ABKeD0GgABRHAezQhARxPV8yh-Q9HxKeDaLIzU4HHJZZiwvIcJKWoKJfepuEkXCpGEyggA

任何帮助都将受到欢迎!

3 个答案:

答案 0 :(得分:1)

  1. 扩展originalData的定义以支持索引器:
const originalData: IOriginalData & { [key: string]: (IUser | IUsersLogin | IUsersUsers)[] }
  1. 将键添加到pushedData时,您必须遍历键访问的项数组,因为它不是单个项:
Object.keys(originalData).forEach(item => {
  const subItems = originalData[item];
  if (subItems) {
    for (const subItem of originalData[item]) {
      pushedData.push({
        data: subItem
      });
    }
  }
});

总体代码如下:

export interface IUser {
  id: string;
  order: number;
}

export interface IUsersLogin {
  result: number;
  UserId: string;
}

export interface IUsersUsers {
  id: number;
  order: number;
}

export interface IOriginalData {
  Users?: IUser[];
  UsersLogins?: IUsersLogin[];
  UsersUsers?: IUsersUsers[];
}

const originalData: IOriginalData & { [key: string]: (IUser | IUsersLogin | IUsersUsers)[] } = {
  Users: [
    {
      id: "e4e2bb46-c210-4a47-9e84-f45c789fcec1",
      order: 1
    },
    {
      id: "b95274c9-3d26-4ce3-98b2-77dce5bd7aae",
      order: 2
    }
  ],
  UsersLogins: [
    {
      result: 1,
      UserId: "e4e2bb46-c210-4a47-9e84-f45c789fcec1"
    },
    {
      result: 0,
      UserId: "b95274c9-3d26-4ce3-98b2-77dce5bd7aae"
    }
  ],
  UsersUsers: [
    {
      id: 1,
      order: 0
    },
    {
      id: 2,
      order: 0
    }
  ]
};

interface IPushedDataItem {
  data: IUser | IUsersLogin | IUsersUsers;
}

type TypePushedData = Array<IPushedDataItem>;

let pushedData: TypePushedData = [];
Object.keys(originalData).forEach(item => {
  const subItems = originalData[item];
  if (subItems) {
    for (const subItem of originalData[item]) {
      pushedData.push({
        data: subItem
      });
    }
  }
});

更新

如果您想更严格地检查字符串键,可以使用in keyof语法进行定义:

const originalData: IOriginalData & { [key in keyof IOriginalData]: (IUser | IUsersLogin | IUsersUsers)[] }

尽管还需要将Object.keys(originalData)的结果强制转换为(keyof IOriginalData)[]

总体代码如下:

export interface IUser {
  id: string;
  order: number;
}

export interface IUsersLogin {
  result: number;
  UserId: string;
}

export interface IUsersUsers {
  id: number;
  order: number;
}

export interface IOriginalData {
  Users?: IUser[];
  UsersLogins?: IUsersLogin[];
  UsersUsers?: IUsersUsers[];
}

export type OriginalDataUnion = IUser | IUsersLogin | IUsersUsers;

const originalData: IOriginalData & { [key in keyof IOriginalData]: OriginalDataUnion[] } = {
  Users: [
    {
      id: "e4e2bb46-c210-4a47-9e84-f45c789fcec1",
      order: 1
    },
    {
      id: "b95274c9-3d26-4ce3-98b2-77dce5bd7aae",
      order: 2
    }
  ],
  UsersLogins: [
    {
      result: 1,
      UserId: "e4e2bb46-c210-4a47-9e84-f45c789fcec1"
    },
    {
      result: 0,
      UserId: "b95274c9-3d26-4ce3-98b2-77dce5bd7aae"
    }
  ],
  UsersUsers: [
    {
      id: 1,
      order: 0
    },
    {
      id: 2,
      order: 0
    }
  ]
};

interface IPushedDataItem {
  data: OriginalDataUnion;
}

type TypePushedData = Array<IPushedDataItem>;

let pushedData: TypePushedData = [];
for (const item of Object.keys(originalData) as (keyof IOriginalData)[]) {
  const subItems = originalData[item];
  if (subItems) {
    for (const subItem of subItems) {
      pushedData.push({
        data: subItem
      });
    }    
  }
}

答案 1 :(得分:0)

那里有两个问题。

originalData没有索引签名。通常,由于TypeScript的静态键入性质,因此高度动态的代码(例如使用Objct.keys通过名称遍历对象的所有属性的代码)往往会出现问题,因为这些属性会丢失静态类型信息具有不同的值类型。如果您可以这样做,那么可能会更好。

另一个问题是,您给pushedData提供了一种类型,该类型期望具有data属性的对象数组,但是您的代码没有这样做,它会推入IUser等对象直接放入数组,而不是将data属性为该IUser等对象的对象压入数组。我怀疑您是说IPushedDataItem是:

type IPushedDataItem  = IUser | IUsersLogin | IUsersUsers;

假设我对此表示正确,则可以这样创建pushedData

let pushedData: TypePushedData = [
    ...(originalData.Users ? originalData.Users : []),
    ...(originalData.UsersLogins ? originalData.UsersLogins : []),
    ...(originalData.UsersUsers ? originalData.UsersUsers : []),
];

Live on the playground

这还具有为您为结果数组中的条目提供已定义顺序的优点(而对于Object.keys,则没有按规范保证顺序的顺序,尽管所有现代引擎对于自己的属性都遵循相同的顺序就像他们对getOwnPropertyKeys所做的那样,只是不能保证。

如果您将属性设置为非可选(将?中的属性中的IOriginalData删除了,则情况就不会那么复杂了)。那将是:

let pushedData: TypePushedData = [
    ...originalData.Users,
    ...originalData.UsersLogins,
    ...originalData.UsersUsers,
];

如果您确实希望对象具有data属性(您的原始版本IPushedDataItem),则可以使用map

let pushedData: TypePushedData = [
    ...(originalData.Users ? originalData.Users.map(data => ({data})) : []),
    ...(originalData.UsersLogins ? originalData.UsersLogins.map(data => ({data})) : []),
    ...(originalData.UsersUsers ? originalData.UsersUsers.map(data => ({data})) : []),
];

Live on the playground

...如果属性不是可选的,那么再次简化。

答案 2 :(得分:0)

让我们简化一下示例:

export interface A { s: string }
export interface B { n: number }
export interface C { b: boolean }

export interface All {
    A: A;
    B: B;
    C: C;
}

//

let all: All = {
    A: {s: "s"},
    B: {n: 33},
    C: {b: true},
};

let other: Array<A | B | C> = [];

现在,这个

Object.keys(all).forEach(k => other.push(all[k]))

将失败,因为Object.keys被推断为Array<string>,而不是人们所期望的Array<keyof All>。有关原因,请参见this comment

但是,同一评论指出for k in obj在使用泛型时将k推断为keyof。我们可以使用它来编写我们自己的Object.keys的严格版本:

function strictKeys<T>(o: T): Array<keyof T> {
    let a = [];
    for (let k in o)
        a.push(k);
    return a;
}

现在

strictKeys(all).forEach(k => other.push(all[k]))

计算正确!

Playground