prabhu
prabhu

Reputation: 369

Type '{}' is not assignable to type 'object[]' during map and reduce in typescript

I have a list of file names in an array. My goal is to read all the files in the array and convert them to json from string.
This is the signature of the readAFile() API.

readAFile(fileName: string, encoding: 'utf-8'): any {
  return fs.readFileSync(filename, {encoding: encoding});
}

Now, I have an array containing absolute path to all the files that is to be read files = ['/path/to/file1', '/path/to/file2', '/path/to/file3'].
I have another function that maps each file name in this list to the readAFile() and reads them and converts them to JSON like this:

readAllFiles(files: string[]): object[] {
  **return** files.map((aFile) => {
    return readAFile(aFile); // this api's return type is 'any'
  })
  .reduce((listOfJson, dataOfOneFile) => {
     const jsonData = JSON.parse(dataOfOneFile);
     listOfJson.push(jsonData);
     return listOfJson;
  }, []);
}

I want to include the file name as well, here's what i added

  files.map((aFile) => {
    const mappedData = {};  // Added this
    mappedData['data'] = readAFile(aFile); // the data now goes into this object
    mappedData['inputFile'] = aFile;
    return mappedData;    <------ ERROR: Type '{}' is not assignable to type 'object[]'.
  })

I figured that the simple fix to this problem is return [mappedData];. I am not sure i understand this correctly. Could some one help me understand why is this an error? Also, any alternative suggestions are welcome.
EDIT 1: added missing return statement in readAllFiles(). typo.

Upvotes: 2

Views: 2123

Answers (1)

artem
artem

Reputation: 51629

The return expression in your readAllFiles is so complex it's hard to see where the error is coming from. Let's split it into intermediate parts: here it's in the form suitable for viewing and compiling in typescript playground (I had to provide fake fs declaration because node types are not available there, and also had to fix encoding parameter for readAFile):

declare var fs: { readFileSync(filename: string, { encoding: string }) };

function readAFile(fileName: string, encoding: string): any {
  return fs.readFileSync(fileName, {encoding: encoding});
}

function readAllFiles(files: string[]): object[] {
  const mf = files.map((aFile) => {
    const mappedData = {};  // Added this
    mappedData['data'] = readAFile(aFile, 'utf8'); // the data now goes into this object
    mappedData['inputFile'] = aFile;
    return mappedData; 
  });

  const rf = mf.reduce((listOfJson, dataOfOneFile) => {
     const jsonData = JSON.parse(dataOfOneFile); // <-- first error is here 
              // JSON.Parse expects string, but dataOfOneFile is an object now

     listOfJson.push(jsonData); // <-- and the real error is here
                // Property 'push' does not exist on type '{}'.
     return listOfJson;
  }, []);

  return rf;
}

Why does it say Property 'push' does not exist on type '{}', when the last parameter of reduce is given as empty array - [], surely it should have a push method?

You see, reduce is declared as overloaded function with 2 variants:

reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => T,
        initialValue?: T): T;

and

reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => U,
         initialValue: U): U;

The first one has initalValue with the same type as array element, T, and for whatever reason it's the declaration chosen by compiler when no types are explicitly given, so in your code the type of initialValue and previousValue is the same as the type of array element - {}.

Why does not it complain when you pass empty array [], when it expects empty object {}?

Because empty array is compatible with it - arrays are objects in javascript, empty object has no properties at all, so any array can be assigned to it.

How to make reduce to return different type?

You can tell the compiler that you want the second variant of reduce - the one with different initialValue type. If you want it to be object[], you can provide it as explicit generic parameter for reduce:

 const rf = mf.reduce<object[]>((listOfJson, dataOfOneFile) => {

But why are you using reduce, when you are not really reducing a list to a single value? If you need to make another list from a list, a single map will do just fine:

function readAllFiles(files: string[]) {
  return files.map((aFile) => {
    return {
      data: JSON.parse(readAFile(aFile, 'utf8')),
      inputFile: aFile
    }
  });
}

the type is inferred as

function readAllFiles(files: string[]): { data: any; inputFile: string; }[]

Upvotes: 4

Related Questions