Search code examples
typescripttypescript-eslint

How to properly fix 'no-implicit-any' when accessing unknown object member?


In a large project, I have a standard reporting module that displays in a grid data, and where the user can define filters and sorting.

Because I don't want to replicate logic, I setup a generic filtering function that can accept data from any type and filter options.

Everything is working perfectly, but ESlint is yelling because of the any usage.

Here's a simplified reproductible code (actual code contains a lot more rules and complexity, like string normalization and data sorting):

// In module utility

type reportState = {
  filterOn : string,
  filterValue : string
}

const filterDataPredicate = (row : any, viewOptions : reportState)=>{

  const dataKey = viewOptions.filterOn as keyof any;

  const valueToCheck = row[dataKey];

  return valueToCheck?.toString() === viewOptions.filterValue;

}

// In consuming module 1
type Point = {
  x: number,
  y: number
};

const graphData: Point[] = [
  { x: 0, y: 20 }, { x: -8, y: 12 }, { x: 100, y: 0 }
]

const currentGraphReportOptions = {
  filterOn : "x",
  filterValue : "0"
};
const filteredGraphData = graphData.filter((row)=>filterDataPredicate(row, currentGraphReportOptions));


console.log(filteredGraphData);

// In consuming module 2
type Customer = {
  firstName: string,
  lastName: string
};

const customerData: Customer[] = [
  { firstName: "John", lastName : "Smith"}, { firstName : "Jane", lastName:"Smith"},{ firstName : "Joe", lastName:"Dalton"}
]

const currentCustomerReportOptions = {
  filterOn : "lastName",
  filterValue : "Smith"
};
const filteredCustomerData = customerData.filter((row)=>filterDataPredicate(row, currentCustomerReportOptions));


console.log(filteredCustomerData);

Everything works as expected, except for the ESLint warning :

warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any

My IDE (VSCode) suggests me to either replace type by unknown or never, but none works.

When using unknown, row[dataKey] is not compiling (Object is of type 'unknown'.(2571)), while when using never, it tells me .toString() is not available on never type (Property 'toString' does not exist on type 'never'.(2339)).

Is there a clean way to fix that?

PS: if it matters, I enable strict : true in my tsconfig.json file, and in my eslint config I have :

{
  "rules": {
    "@typescript-eslint/no-explicit-any": "error"
  }
}

Solution

  • I managed to fix the issue (w/o disabling the rule)

    The correct type for row object should Record<string, unknown>.

    I think this is working because my incoming row will be non empty objects and nothing else (no base type, ...). Whatever the object values will be, it's always identified by an object key.

    Updated code :

    // In module utility
    
    
    type reportState = {
      filterOn : string,
      filterValue : string
    }
    
    const filterDataPredicate = (row : Record<string, unknown>, viewOptions : reportState)=>{
    
      const dataKey = viewOptions.filterOn;
    
      const valueToCheck = row[dataKey];
    
      return `${valueToCheck}` === viewOptions.filterValue;
    
    }
    
    // In consuming module 1
    type Point = {
      x: number,
      y: number
    };
    
    const graphData: Point[] = [
      { x: 0, y: 20 }, { x: -8, y: 12 }, { x: 100, y: 0 }
    ]
    
    const currentGraphReportOptions = {
      filterOn : "x",
      filterValue : "0"
    };
    const filteredGraphData = graphData.filter((row)=>filterDataPredicate(row, currentGraphReportOptions));
    
    
    console.log(filteredGraphData);
    
    // In consuming module 2
    type Customer = {
      firstName: string,
      lastName: string
    };
    
    const customerData: Customer[] = [
      { firstName: "John", lastName : "Smith"}, { firstName : "Jane", lastName:"Smith"},{ firstName : "Joe", lastName:"Dalton"}
    ]
    
    const currentCustomerReportOptions = {
      filterOn : "lastName",
      filterValue : "Smith"
    };
    const filteredCustomerData = customerData.filter((row)=>filterDataPredicate(row, currentCustomerReportOptions));
    
    
    console.log(filteredCustomerData);