I have a db-config file in the following form:
const simpleDbConfig: DbConfig = {
firstTable: {
columns: {
foo: { nameDb: "Foo", dataType: "TEXT" },
bar: { nameDb: "Bar", dataType: "TEXT" },
},
},
};
And also the following type definitions:
interface DbConfig {
firstTable: DbTable<"foo" | "bar">;
}
interface DbTable<T extends string> {
columns: ColumnObject<T>;
}
type ColumnObject<T extends string> = Record<T, Column>;
interface Column {
nameDb: string;
dataType: string;
}
Now what I am trying to do is write a function, that gets the key of a table and an array of keys of columns of this table as well as a property of those columns. It should return an object mapping the column keys to the value of the passed in property of those columns.
Here's the function:
Version 1:
function getColKeyToPropMap<
TDbTblJsName extends keyof DbConfig,
TDbTblColObjRecord extends DbConfig[TDbTblJsName]["columns"],
TDColJsName extends keyof TDbTblColObjRecord,
TDbColObj extends TDbTblColObjRecord[TDColJsName],
TColPropName extends keyof TDbColObj
>(tbl: TDbTblJsName, cols: TDColJsName[], colPropName: TColPropName) {
const tblInfo: TDbTblColObjRecord = simpleDbConfig[tbl].columns as TDbTblColObjRecord;
const resObj: Record<TDColJsName, TDbColObj[TColPropName]> = {} as Record<TDColJsName, TDbColObj[TColPropName]>;
for (let col of cols) {
resObj[col] = tblInfo[col][colPropName];
}
return resObj;
}
Version 2:
function getColKeyToPropMap<
TDbTblJsName extends keyof DbConfig,
TDbTblColObjRecord extends DbConfig[TDbTblJsName]["columns"],
TDbColObj extends TDbTblColObjRecord[keyof TDbTblColObjRecord]
>(tbl: TDbTblJsName, cols: Array<keyof TDbTblColObjRecord>, colPropName: keyof TDbColObj) {
const tblInfo: TDbTblColObjRecord = simpleDbConfig[tbl].columns as TDbTblColObjRecord;
const resObj: Record<keyof TDbTblColObjRecord, TDbColObj[keyof TDbColObj]> = {} as Record<keyof TDbTblColObjRecord, TDbColObj[keyof TDbColObj]>;
for (let col of cols) {
resObj[col] = tblInfo[col][colPropName];
}
return resObj;
}
Here is an exemplary function call:
const test = getColKeyToPropMap("firstTable", ["foo"], "nameDb");
// test should be
// {foo : Foo}
I get the following typescript error regarding this line within the for loop: "resObj[col] = tblInfo[col][colPropName];":
"Type 'TColPropName' cannot be used to index type 'TDbTblColObjRecord[TDbColJsName]'."
Where is my mistake?
Thanks for your time and help!
I tried to access nested types with indexed access. Despite the access being valid in javascript, it somehow is not in typescript.
Your code is of this form:
and doesn't work because
tblInfo[col]is of typeC[P]butcolPropnameis of typeQ extends keyof V. And whileVis constrained toC[P], that's not the same as saying it isC[P].V extends C[P]means thatVcan have many more properties thanC[P], and thus a key ofVmight not be a key ofC[P].This code has more generic type parameters than are needed in your function. Usually you only want no more than a single type parameter per function parameter (there are exceptions but the point is that you want the type arguments to be inferrable from the function arguments directly). If you find yourself wanting to add a type parameter just to be an alias for an annoying-to-write-out type, you can mitigate that by creating an appropriate generic type alias to cut down on some (but probably not all) of the redundancy. For example:
I've used
ColsFor<K>to be an alias ofDbConfig[K]["columns"]so that doesn't have to show up a bunch of times, and this makes it a little less painful to give up our extra type parameters.And now the code works;
colPropNameis of typeQ extends keyof ColsFor<K>[P]andtblInfo[col]is of typeColsFor<K>[P]. That meanscolPropNameis known to be a key oftblInfo[col]. And so both sides of that assignment are seen to be of typeColsFor<K>[P][Q]and everything compiles as desired.Playground link to code