Will Howell
Will Howell

Reputation: 3715

Casting an interface to a dictionary

I have a function logNumbers that accepts a dictionary where all keys are strings and all values are numbers. I want to call logNumbers with an object belongs to a stricter interface but still satisfies those conditions.

interface NumberDictionary {
  [key: string]: number;
}

interface StatisticsResult {
  mean: number;
  mode: number;
  median: number;
}

function logNumbers(numDict: NumberDictionary) { ... }

const results: StatisticsResult = {
  mean: 1,
  mode: 2,
  median: 2,
};

// 
// Error:
//   Argument of type 'StatisticsResult' is not assignable to parameter of type 'NumberDictionary'.
//   Index signature is missing in type 'StatisticsResult'.
//
logNumbers(results);

I would like for StatisticsResult to remain the same and to somehow modify the signature for logNumbers. Is there a way to do this? Perhaps I could signal to typescript that no new keys will be added to numDict within logNumbers?

Reproduction in TypeScript Playground

Upvotes: 3

Views: 1538

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250376

If your goal is to restrict the function to an object with just number properties, you can use a generic type parameter constrained to be a record with only numbers.

  interface StatisticsResult {
    mean: number;
    mode: number;
    median: number;
  }

  function logNumbers<T extends Record<keyof T, number>>(num: T) {
    // Log the numbers as a table or something
  }

  const results: StatisticsResult = {
    mean: 1,
    mode: 2,
    median: 2,
  };

  //ok
  logNumbers(results);

Upvotes: 3

Robbie Milejczak
Robbie Milejczak

Reputation: 5780

All you need to do here is:

interface StatisticsResult extends NumberDictionary {
  mean: number;
  mode: number;
  median: number;
}

and now StatisticsResult will use the index signature you defined. TypeScript doesn't care about you adding new keys, it only cares that you're assigning a type with an index signature to a type without one.

You could also cast results, though this is much more hacky since you need to cast to unknown first (if you don't, ts will complain that your types don't have any overlap because of the index signagure disparity) and kind of defeats the purpose:

logNumbers((results as unknown) as NumberDictionary);

And lastly, though you said you'd rather not alter StatisticalResult, you could define the index signature there as well (which I understand also defeats the purpose of having your NumberDictionary type anyway:

interface StatisticsResult{
  [key: string]: number;
  mean: number;
  mode: number;
  median: number;
}

Upvotes: 0

Related Questions