slick_reaper
slick_reaper

Reputation: 192

Typescript eslint and strict_check errors due to any keyword

Code snippet below with comments explaining the code.

  // The function here is to check whether the two arguments are equivalent or
  // not empty or not. So, we cannot have any specific type defined for the
  // arguments and the arguments are given a generic type of Object.
  /**
   * @param {Object} a - the first object to be compared.
   * @param {Object} b - the second object to be compared.
   * @return {boolean} - true if a is equivalent to b, false otherwise.
   */
  isEquivalent(a: Object, b: Object): boolean {
    if (a === null || b === null) {
      return a === b;
    }
    if (typeof a !== typeof b) {
      return false;
    }
    if (typeof a !== 'object') {
      return a === b;
    }
    // Create arrays of property names.
    var aProps = Object.getOwnPropertyNames(a);
    var bProps = Object.getOwnPropertyNames(b);
    if (aProps.length !== bProps.length) {
      return false;
    }
    for (var i = 0; i < aProps.length; i++) {
      var propName = aProps[i];
      if (!this.isEquivalent(a[propName], b[propName])) {
        return false;
      }
    }
    return true;
  }

I'm running this with typescript checks --strict_checks, which gives me the following error message: error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Object'. No index signature with a parameter of type 'string' was found on type 'Object'.

When I modify the for loop to the following:

for (var i = 0; i < aProps.length; i++) {
    var propName = aProps[i];
    const aObj: {[index: string]: any} = a;
    const bObj: {[index: string]: any} = b;
    if (!this.isEquivalent(aObj[propName], bObj[propName])) {
      return false;
    }
 }

the --strict_check errors are fixed. However, I now receive typescript eslint errors since the use of keyword "any" is prohibited in eslint. Can anyone point me in the right direction on getting around these errors without disabling the checks?

Upvotes: 0

Views: 583

Answers (1)

Brother Woodrow
Brother Woodrow

Reputation: 6372

You shouldn't use Object as a type. You can use object (see the docs here), but you'll still get the any-error when using loops and accessing properties because object isn't indexable. The solution is to provide your own interface:

interface IndexableObject {
    [key: string]: string | IndexableObject
}

Assuming the objects you're comparing are possibly recursive, you can define the IndexableObject's value as either a string, or itself another IndexableObject.

Another thing that leads to errors is the fact you're checking if a is an object, but not if b is an object as well. The compiler will complain because you will later possibly be accessing a string as if it were an object (a[propName/b[propName]). While that will actually work, because in JS everything is an object (so 'foo'[0] will yield "f"), the TypeScript compiler won't like it. So you should check for that as well.

With these changes, your function will work as expected:

interface IndexableObject {
    [key: string]: string | IndexableObject
}

// The function here is to check whether the two arguments are equivalent or
// not empty or not. So, we cannot have any specific type defined for the
// arguments and the arguments are given a generic type of Object.
/**
 * @param {Object} a - the first object to be compared.
 * @param {Object} b - the second object to be compared.
 * @return {boolean} - true if a is equivalent to b, false otherwise.
 */
function isEquivalent(a: IndexableObject | string, b: IndexableObject | string): boolean {
    if (a === null || b === null) {
      return a === b;
    }
    if (typeof a !== typeof b) {
      return false;
    }
    if (typeof a !== 'object' || typeof b !== 'object') {
      return a === b;
    }
    // Create arrays of property names.
    var aProps = Object.getOwnPropertyNames(a);
    var bProps = Object.getOwnPropertyNames(b);
    if (aProps.length !== bProps.length) {
      return false;
    }
    for (var i = 0; i < aProps.length; i++) {
      var propName = aProps[i];
      if (!isEquivalent(a[propName], b[propName])) {
        return false;
      }
    }
    return true;
  }

  const ex1 = { 'foo': 'bar' };
  const ex2 = { 'foo': 'bar' };
  const ex3 = { 'foo': 'foo' };
  const ex4 = { 'foo': { 'foo2': 'foo3' } };
  const ex5 = { 'foo': { 'foo2': 'foo3' } };
  const ex6 = { 'foo': { 'foo2': 'foo4' } };

  console.log(isEquivalent(ex1, ex2)); // true
  console.log(isEquivalent(ex1, ex3)); // false
  console.log(isEquivalent(ex4, ex5)); // true
  console.log(isEquivalent(ex4, ex6)); // false

By the way, if you have Lodash in your project, they have solved this problem quite nicely already. It is my experience "rolling your own" is more fun, but you'll sooner or later run into edge cases others have already tackled, at which point the fun pretty much stops. That's just my $0.02 though.

Upvotes: 1

Related Questions