FYI, as an alternative to a string index signature like
interface FutureProjection {
[k: string]: number
}
which would accept any key whatsoever:
declare const f: FutureProjection
f.oopsiedaisy = 123; // no compiler error
one might consider using a template string pattern index signature as introduced in TypeScript 4.4 to accept only strings that start with "month" and are followed by a numeric-like string:
interface FutureProjection {
[k: `month${number}`]: number
}
declare const f: FutureProjection
f.oopsiedaisy = 123; // error
f.month11 = 456; // okay
f.month1234567 = 789; // this is also okay, any number is accepted
If you do it this way, then the compiler will automatically allow you to index into a FutureProjection with an appropriately constructed template literal string:
function functionToSetMonthData(currentDate: string, futureDate: string,
projection: FutureProjection, amount: number
): FutureProjection {
const monthNum = calculateMonthNum(currentDate, futureDate);
projection[`month${monthNum}`] = amount; // okay
return projection;
}
Note that this doesn't quite serve the purpose of only accepting month1 through month12. You could decide to replace number with a union of numeric literal types,
type MonthNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
interface FutureProjection extends Record<`month${MonthNumber}`, number> { }
declare const f: FutureProjection
f.oopsiedaisy = 123; // error
f.month11 = 456; // okay
f.month1234567 = 789; // error
which is great; unfortunately you'd have a hard time convincing the compiler that any of your mathematical operations that produce number are really producing a MonthNumber, since it doesn't really do math at the type level (see microsoft/TypeScript#15645):
declare const i: MonthNumber;
const j: MonthNumber = 13 - i; // error, even though this must be safe
const k: MonthNumber = ((i + 6) % 12) || 12; // error, ditto
And so you'd find yourself doing unsafe assertions no matter what as well as some other workarounds:
function calculateMonthNum(currentDate: string, futureDate: string): MonthNumber {
return (((((new Date(futureDate).getMonth() - new Date(currentDate).getMonth())
% 12) + 12) % 12) || 12) as MonthNumber // <-- assert here
}
function functionToSetMonthData(currentDate: string, futureDate: string,
projection: FutureProjection, amount: number
): FutureProjection {
const monthNum = calculateMonthNum(currentDate, futureDate);
projection[`month${monthNum}` as const] = amount; // <-- const assert here
return projection;
}
const f = {} as FutureProjection; // assert here
for (let i = 1; i <= 12; i++) f[`month${i as MonthNumber}`] = 0; // assert here
So I'm kind of thinking you'd be better off with `month${number}`.
Playground link to code