Reputation: 538
I have an object A and I want to create a new object B that contains some of the fields of object A. It's a pretty straightforward task however I still cannot figure out how to type it correctly. It's better to explain it with code:
interface Data {
userName: string
photoUrl: string
age: number
}
const data: Data = {
userName: 'John',
photoUrl: 'https://...',
age: 100
}
// new data can look like for example {userName: 'John'}
const newData: Partial<Data> = {}
// 1. solution - doesn't work because key is of type string
for (const key in data) {
/* if (//some complex condition ) */
newData[key] = data[key]
}
// 2. solution - keys are typed correctly but still doesn't work properly
const keys = Object.keys(data) as Array<keyof typeof data>
keys.forEach(key => {
/* if (//some complex condition ) */
newData[key] = data[key]
})
// Anything else I am missing? :)
Playground here
Upvotes: 1
Views: 223
Reputation: 7186
There is no built-in way to iterate over narrowly typed object keys (type keyof T
rather than string
) of an object. Here's a great article that explores some approaches to this TS problem.
A few solutions:
Object.entries
and assert the key's type:for (const [str, value] of Object.entries(data)) {
const key = str as keyof typeof data;
// ^^^ : keyof Data
newData[key] = value;
// ^^^^^ : any
}
for
statement:// This one is similar to casting the result of `Object.keys`, but a bit less verbose.
let key: keyof Data;
for (key in data) {
newData[key] = data[key] as any;
}
Note: if your object has more than one property type, you have to cast the value to any
to make it work on an assignment, or you will get an error like Type 'string | number' is not assignable to type 'undefined'.
. In your example, given key
with type keyof Data
, TSC can infer that the value of data[key]
is either a number
or a string
. However, it cannot infer which key is being referenced during a given iteration of for (key in data)
, which means it cannot determine if the value of data[key]
is the same type as newData[key]
. If age
was a string
(so that every property was a string
type), it would work as expected.
For example:
interface I1 {
a: string
b: number
}
interface I2 {
a: string
b: string
}
const i1: I1 = { a: 'a', b: 0 }
let key: keyof I1;
for(key in i1) {
i1[key] = i1[key];
//^^^^^^^ Error: is this a number or a string? And which do I need?
}
const i2: I2 = { a: 'a', b: 'b' }
let key2: keyof I2;
for(key2 in i1) {
i2[key2] = i2[key2]; // Ok: I am getting a string and I need a string.
}
Upvotes: 1