We work with an API that requires us to send off an object along with additional properties to tell it which properties on that object are to be modified by ading _X
and setting it to true
. For example if we had this object
{ firstName: 'Joe', lastName: 'Smith' }
and wanted to only change the lastName
property, we would send this:
{firstName: 'Joe', lastName: 'Smith', lastName_X: true }
I'd like to set up strong typing for this in TypeScript, but I seem to be missing something
type ApiBoolean<T extends object> = {
[index: `${K in keyof T}_X`]: true; // <== Error is here
};
function convertForApi<T extends object>(obj: T, keys: Array<keyof T>): ApiBoolean<T> {
const returnObj: ApiBoolean<T> = {...obj}
for(let k of keys){
returnObj[`${k}_X`] = true;
}
return returnObj;
}
const person = {
firstName: 'Joe',
lastName: 'Smith',
age: 35
};
const converted = convertForApi(person, ['lastName', 'age'])
Typescript Playground Link
I need to allow the indexer to have property suffixes. What am I doing wrong and how can I achieve this?
We work with an API that requires us to send off an object along with additional properties to tell it which properties on that object are to be modified by ading _X
and setting it to true
. For example if we had this object
{ firstName: 'Joe', lastName: 'Smith' }
and wanted to only change the lastName
property, we would send this:
{firstName: 'Joe', lastName: 'Smith', lastName_X: true }
I'd like to set up strong typing for this in TypeScript, but I seem to be missing something
type ApiBoolean<T extends object> = {
[index: `${K in keyof T}_X`]: true; // <== Error is here
};
function convertForApi<T extends object>(obj: T, keys: Array<keyof T>): ApiBoolean<T> {
const returnObj: ApiBoolean<T> = {...obj}
for(let k of keys){
returnObj[`${k}_X`] = true;
}
return returnObj;
}
const person = {
firstName: 'Joe',
lastName: 'Smith',
age: 35
};
const converted = convertForApi(person, ['lastName', 'age'])
Typescript Playground Link
I need to allow the indexer to have property suffixes. What am I doing wrong and how can I achieve this?
Share Improve this question asked Feb 4 at 13:07 Chris BarrChris Barr 34.1k28 gold badges102 silver badges152 bronze badges 1 |2 Answers
Reset to default 1You could use an intersection of 2 mapped types:
Playground
type StringKeys<T extends object> = keyof {[K in keyof T as K extends string ? K : never]: unknown} & string;
type ApiBoolean<T extends object, KK extends StringKeys<T>[]> =
{[K in keyof T]: T[K]} &
{[K in KK[number] as `${K}_X`]: true} extends infer A ? A : never;
function convertForApi<T extends object, const K extends StringKeys<T>[]>(obj: T, keys: K) {
return keys.reduce((r, key) => (r[`${key}_X`] = true, r), {...obj} as any) as ApiBoolean<T, K>;
}
I think the issue is in the way you're using the literal types in your function. The way you're doing it is causing a TypeScript error because of how you're defining the keys in the mapped type.
I think this should fix your problem.
type ApiBoolean<T extends object> = {
[K in keyof T as `${K & string}_X`]: true;
};
Record
as shown in this playground link. Note how your initialization ofreturnObj
is incorrect according to the typing, and so callingconvertForApi(person, [])
will return a completely inappropriate result. That's probably out of scope for the question, but it would help if you edit to remove distractions like this. Does this fully address the question? If so I'll write an answer or find a duplicate. If not, what's missing? – jcalz Commented Feb 4 at 14:49