Reputation:
Using typescript to type an array of objects:
Here are the interfaces:
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[];
}
Here I create an object using this interfaces:
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
}
]
};
And here I manipulate the data of this object to push it into another object:
interface IPushedDataItem {
data: IUser | IUsersLogin | IUsersUsers;
}
type TypePushedData = Array<IPushedDataItem>;
let pushedData: TypePushedData = [];
Object.keys(originalData).forEach(item => {
pushedData.push({
data: originalData[item]
});
});
In this process, I cant type pushedData
properly, and it complains with data:originalData[item]
.
You can find this in the TypeScript playgound:
Any help will be welcome!
Upvotes: 4
Views: 564
Reputation: 214949
Let's simplify your example a bit:
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> = [];
Now, this
Object.keys(all).forEach(k => other.push(all[k]))
will fail, because Object.keys
is inferred as Array<string>
and not Array<keyof All>
as one might expect. See this comment as to why.
However, the same comment says that for k in obj
infers k
as keyof
when using generics. We can use that to write our own strict version of Object.keys
:
function strictKeys<T>(o: T): Array<keyof T> {
let a = [];
for (let k in o)
a.push(k);
return a;
}
and now
strictKeys(all).forEach(k => other.push(all[k]))
computes correctly!
Upvotes: 0
Reputation: 3957
originalData
to support indexer:const originalData: IOriginalData & { [key: string]: (IUser | IUsersLogin | IUsersUsers)[] }
pushedData
because it's not a single item:Object.keys(originalData).forEach(item => {
const subItems = originalData[item];
if (subItems) {
for (const subItem of originalData[item]) {
pushedData.push({
data: subItem
});
}
}
});
the overall code is the following:
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
});
}
}
});
UPDATE
If you want to have a more strict check of string key, you can define it using in keyof
syntax:
const originalData: IOriginalData & { [key in keyof IOriginalData]: (IUser | IUsersLogin | IUsersUsers)[] }
Though it will also require to cast the result of Object.keys(originalData)
to (keyof IOriginalData)[]
.
The overall code will be the following:
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
});
}
}
}
Upvotes: 1
Reputation: 1074088
There are a couple of problems there.
originalData
doesn't have an index signature. In general, because of TypeScript's static typing nature, highly-dynamic code (such as code going through all the properties of an object by name using Objct.keys
) tends to be problematic because the static type information is lost, since the properties have different value types. If you can void doing that, which you can in your case, that's probably better.
Another issue is that you've given pushedData
a type that expects an array of objects with a data
property, but your code doesn't do that, it pushes the IUser
etc. objects directly into the array instead of pushing objects where the data
property is that IUser
etc. object. I suspect you meant IPushedDataItem
to be:
type IPushedDataItem = IUser | IUsersLogin | IUsersUsers;
Assuming I'm correct about that, you can then create pushedData
like this:
let pushedData: TypePushedData = [
...(originalData.Users ? originalData.Users : []),
...(originalData.UsersLogins ? originalData.UsersLogins : []),
...(originalData.UsersUsers ? originalData.UsersUsers : []),
];
This also has the advantage of giving you a defined order to the entries in the resulting array (whereas with Object.keys
, there is no guaranteed order per the specification, although all modern engines follow the same order for own properties as they would for getOwnPropertyKeys
, it's just not guaranteed).
That would be less complicated if you made the properties non-optional (removed the ?
in the properties in IOriginalData
). Then it would be just:
let pushedData: TypePushedData = [
...originalData.Users,
...originalData.UsersLogins,
...originalData.UsersUsers,
];
If you really want objects with a data
property instead (your original version of IPushedDataItem
), then you'd use 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})) : []),
];
...which again would be simpler if the properties weren't optional.
Upvotes: 0