Search code examples
typescriptimmutable.js

Type 'string' is not assignable to type keyof in Immutable.js


I got this typing error when trying to use immutable js reviver from fromJS function.

Check this TS playground, I could reproduce it there.

interface User {
  name: string;
  age: number;
}

// Taken from typing definition of Immutable.js with some modification to simplify it
// https://github.com/immutable-js/immutable-js/blob/main/type-definitions/immutable.d.ts#L4955
function fromJS(
    jsValue: unknown,
    reviver?: (
      key: string | number,
      value: ''      
    ) => unknown
  ): unknown {
    return '';
  };

// My custom function for reviver, 
// I used `Extract` to ensure that it gets the string only
export function modelReviver<T>(mapper: Partial<T>): (key: Extract<keyof T, string>, value: any) => any {  
  return (key, value) => (mapper.hasOwnProperty(key) ? mapper[key] : fromJS(value));
}

const model = modelReviver<User>({
  name: 'thomas'
});

fromJS({}, model) // typing error

The error said

Types of parameters 'key' and 'key' are incompatible.
  Type 'string | number' is not assignable to type 'keyof User'.
  Type 'string' is not assignable to type 'keyof User'

I'm aware that the issue is regarding the key parameter and since the key is from Immutable.js I couldn't just modify it. Wondering why Extract that I specified on modelReviver couldn't solve the issue. 🤔

Does anyone have a clue why this happened? Thank you


Solution

  • Without being able to pass in a generic to fromJS, the only way to do this, is to type the key as what's expected string | number.

    Here is a sample bellow:

    interface User {
      name: string;
      age: number;
    }
    
    function fromJS(
        jsValue: unknown,
        reviver?: (
          key: string | number,
          value: ''      
        ) => unknown
      ): unknown {
        return '';
      };
    
    export function modelReviver<T>(mapper: Partial<T>): (key: string | number, value: any) => any {  
      return (key, value) => (mapper.hasOwnProperty(key) ? mapper[key as keyof T] : fromJS(value));
    }
    
    const model = modelReviver<User>({
      name: 'thomas'
    });
    
    fromJS({}, model)