I have a typescript interface which is defined as:
interface FutureProjection{
month1: number;
month2: number;
.
.
.
.
.
.
month12: number;
}
This data is used to show the grid on the UI. I have a logic which is used to find out which month to set data:
calculateMonthNum(currentDate:String,futureDate: String):number{
//Calculate the month difference between future and current.
return monthDiff;
}
functionToSetMonthData(currentDate:String,futureDate: String, projection: FutureProjection, amount: number): FutureProjection{
const monthNum = calculateDiff(currentDate,futureDate);
****//How to do this?? vvvv **
projection.month+monthNum = amount; //. <---This assignment operation???
return projection;
}
I have a typescript interface which is defined as:
interface FutureProjection{
month1: number;
month2: number;
.
.
.
.
.
.
month12: number;
}
This data is used to show the grid on the UI. I have a logic which is used to find out which month to set data:
calculateMonthNum(currentDate:String,futureDate: String):number{
//Calculate the month difference between future and current.
return monthDiff;
}
functionToSetMonthData(currentDate:String,futureDate: String, projection: FutureProjection, amount: number): FutureProjection{
const monthNum = calculateDiff(currentDate,futureDate);
****//How to do this?? vvvv **
projection.month+monthNum = amount; //. <---This assignment operation???
return projection;
}
Share
Improve this question
edited Aug 27, 2021 at 17:38
eko
40.7k11 gold badges78 silver badges101 bronze badges
asked Aug 27, 2021 at 17:10
RV.RV.
3,0184 gold badges31 silver badges49 bronze badges
5 Answers
Reset to default 4You can always use bracket syntax to access a property of any javascript object.
// identical
projection['month1']
projection.month1
Which means you can build a string that is the property name and use that in brackets:
projection[`month${monthNum}`] = amount;
But in typescript that only get you half way there. This will get you a type error:
type 'string' can't be used to index type 'FutureProjection'.
The problem is that FutureProjection
cannot be indexed by string
, only specific strings are allowed. So typescript doesn't know that month${monthNum}
is a specific string that is actually valid since monthNum
could be any number (-5, 0, 100, 3.14159, etc).
The easiest way to solve that is cast the key string to a keyof FutureProjection
. Note that in order for this to be safe you must ensure that calculateDiff()
only ever returns integers 1 through 12.
That looks something like:
function setMonthData(currentDate:String,futureDate: String, projection: FutureProjection, amount: number): FutureProjection{
const monthNum = calculateDiff(currentDate, futureDate);
const monthKey = `month${monthNum}` as keyof FutureProjection
projection[monthKey] = amount;
return projection;
}
Working example on typescript playground
All that said, it may make your life easier to use an array instead.
You can achieve that like the following:
projection[`month${monthNum}`] = amount;
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 piler 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 piler 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 piler 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
I would do like this...
projection[`month${monthNum }`] = amount;
Try to define the interface like
interface FutureProjection {
[month: string]: number
}
then set values into the object using template strings
projection[`month${monthNum}`] = amount;