I have a function that doing something like this
function transform(obj) {
const returnedObj = { custom: {} };
for (key in obj.wrap.custom) {
returnedObj.custom[key] = obj.wrap.custom[key];
}
return returnedObj;
}
What is the right way to describe types? I have tried in this way
interface ITransformingObject {
wrap: {
custom: {[key: string]: string}
}
}
interface IReturnedObject {
custom: ITransformingObject['wrap']['custom']
}
function transform(obj: ITransformingObject): IReturnedObject {
const returnedObj = { custom: {} };
for (const key in obj.wrap.custom) {
returnedObj.custom[key] = obj.wrap.custom[key];
}
return returnedObj;
}
const a = {
wrap: {
custom: {
test: 'test'
}
}
}
const b = transform(a);
But I'm not sure that it's correct. For example, IntelliSense doesn't see the properties of a.custom
. Can I better describe the return type to define that keys of a.custom
must be the same as keys of a.wrap.custom
?
CodePudding user response:
In order to keep track of the specific object types inside the custom
properties, you need to use generics, like this:
interface ITransformingObject<T extends object> {
wrap: {
custom: T
}
}
interface IReturnedObject<T extends object> {
custom: T
}
function transform<T extends object>(obj: ITransformingObject<T>): IReturnedObject<T> {
const returnedObj = { custom: {} } as IReturnedObject<T>;
for (const key in obj.wrap.custom) {
returnedObj.custom[key] = obj.wrap.custom[key];
}
return returnedObj;
}
Here an ITransformingObject<T>
has a wrap
property with a custom
property of type T
, for some objectlike T
you specify. And IReturnedObject<T>
is the same but without the wrap
wrapper. Then transform()
is also generic; its parameter is of type ITransformingObject<T>
for some T
, and then returns a value of type IReturnedObject<T>
for the same T
.
Also note that for this to compile with no error I needed to assert that returnedObject
is of type IReturnedObject<T>
, because it isn't initially true (certainly at initialization, the value {}
is unlikely to be of type T
).
Now, when you call transform()
, the compiler will infer the type argument T
for you based on the type of the value you pass in:
const a = {
wrap: {
custom: {
test: 'test'
}
}
}
const b = transform(a);
/* function transform<{
test: string;
}>(obj: ITransformingObject<{
test: string;
}>): IReturnedObject<{
test: string;
}> */
See how T
is inferred as {test: string}
, and so b
is of type IReturnedObject<{test: string}>
. Meaning the compiler now knows about what properties are expected to be present or not expected to be present in b.custom
:
console.log(b.custom.test.toUpperCase()); // compiles fine, "TEST"
b.custom.nope // compiler error, nope does not exist on {test: string}