Anish Sinha
Anish Sinha

Reputation: 305

How can I selectively copy properties from one object to another in TypeScript

I'm trying to selectively copy key-value pairs from one object to another, so I can selectively remove properties of an object, like so:

sanitizeBody: function (obj: object, ...allowed: string[]) {
    const sanitizedObject = {};
    Object.keys(obj).forEach((el: string) => {
      if (allowed.includes(el)) {
        sanitizedObject[el] = obj[el];
      }
    });
  }

Basically, the point of the function is that it takes an object, i.e. req.body, and a list of strings, and performs this operation. For example, an example input may look like: sanitizeBody(req.body, 'role', 'active', 'status').

This worked in Javascript,however, in TypeScript I keep getting the error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.
  No index signature with a parameter of type 'string' was found on type '{}'.

I'm not sure how to fix this.

Upvotes: 3

Views: 2047

Answers (3)

bloo
bloo

Reputation: 1550

Here is a solution that implicitly selects the keys you provide properly and maintains the typings with the output.

// We have to make sure that the object you provided extends a record whose keys
// are of type string, im pretty sure there is a better way to do that but the
// use of any sufficient.
function sanitize<T extends Record<string, any>, K extends keyof T>(obj: T, ...keys: Array<K>): Pick<T, K> {

  // we then apply your desired code as provided, its still the same as how you did
  // it, just a different approach
  return Object.entries(obj).reduce((a, [k, v]) => {

    // I had to cheat and use find because the includes function only accepts
    // string values but the way the argument is layed out makes it possible for
    // K to be boolean
    // if (keys.includes(k)) { type error here
    if (keys.find((a) => a === k)) {
      a[k as K] = v;
    }
    return a;
  }, {} as Pick<T, K>);
}

// So when we test it here we should get the desired output.
const test = {
  a: 1,
  b: 4,
  c: 6
};

const output_a = sanitize(test, 'a', 'b');

// type error here
output_a.c;
// no type error here
output_a.a;

console.log(output_a);

Note that though there are generics defined in the function, you do not have to specify them when calling because your arguments imply the generic being used. Hence the generics are implicit.

Here is a link to my playground

Upvotes: 3

Subrato Pattanaik
Subrato Pattanaik

Reputation: 6049

Index signature in typescript

Typescript says that when we don't know the name of the properties but we do know the shape of the values then in that case we can provide an index signature to describe those values along with the type of index.

In your case, typescript doesn't understand the index signature of object type so you need to explicitly define the index signature for your object. Below is the idiomatic way to define it.

type IndexSignature = {
  [key: string]: string; //in your case value is string
}

Now, you have to do some changes as we have IndexSignature.

sanitize function(obj: IndexSignature, ...allowed: string[]) {
    const sanitizedObject = {} as IndexSignature;
    Object.keys(obj).forEach((el: string) => {
      if (allowed.includes(el)) {
        sanitizedObject[el] = obj[el];
      }
    });
  }

Upvotes: 2

razdi
razdi

Reputation: 1440

Trying to reuse as much of your code, here is what would work in TS:

const allowed: Array<string> = ['key1', 'key3']
const body = {
  'key1': 1,
  'key2': 2,
  'key3': 3
}

function sanitizeBody (obj: Record<string, any>, allowed: Array<string>): Record<string, any> {
  const sanitizedObject: Record<string, any> = {};
  Object.keys(obj).forEach((el: string) => {
    if (allowed.includes(el)) {
      sanitizedObject[el] = obj[el];
    }
  })
  return sanitizedObject
}

const output = sanitizeBody(body, allowed)

Output:

{
  "key1": 1,
  "key3": 3
} 

If you provide ts the correct types, it should work as you expect.

Upvotes: 2

Related Questions