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}
