Home > OS >  Is there way to describe function for transforming object?
Is there way to describe function for transforming object?

Time:06-24

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}

Playground link to code

  • Related