最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to add typescript index signatures with a suffix? - Stack Overflow

programmeradmin0浏览0评论

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
  • You can't make an index signature unless you're sure the key is appropriate for an index signature. Instead you really just want Record as shown in this playground link. Note how your initialization of returnObj is incorrect according to the typing, and so calling convertForApi(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
Add a comment  | 

2 Answers 2

Reset to default 1

You 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;
};
发布评论

评论列表(0)

  1. 暂无评论
ok 不同模板 switch ($forum['model']) { /*case '0': include _include(APP_PATH . 'view/htm/read.htm'); break;*/ default: include _include(theme_load('read', $fid)); break; } } break; case '10': // 主题外链 / thread external link http_location(htmlspecialchars_decode(trim($thread['description']))); break; case '11': // 单页 / single page $attachlist = array(); $imagelist = array(); $thread['filelist'] = array(); $threadlist = NULL; $thread['files'] > 0 and list($attachlist, $imagelist, $thread['filelist']) = well_attach_find_by_tid($tid); $data = data_read_cache($tid); empty($data) and message(-1, lang('data_malformation')); $tidlist = $forum['threads'] ? page_find_by_fid($fid, $page, $pagesize) : NULL; if ($tidlist) { $tidarr = arrlist_values($tidlist, 'tid'); $threadlist = well_thread_find($tidarr, $pagesize); // 按之前tidlist排序 $threadlist = array2_sort_key($threadlist, $tidlist, 'tid'); } $allowpost = forum_access_user($fid, $gid, 'allowpost'); $allowupdate = forum_access_mod($fid, $gid, 'allowupdate'); $allowdelete = forum_access_mod($fid, $gid, 'allowdelete'); $access = array('allowpost' => $allowpost, 'allowupdate' => $allowupdate, 'allowdelete' => $allowdelete); $header['title'] = $thread['subject']; $header['mobile_link'] = $thread['url']; $header['keywords'] = $thread['keyword'] ? $thread['keyword'] : $thread['subject']; $header['description'] = $thread['description'] ? $thread['description'] : $thread['brief']; $_SESSION['fid'] = $fid; if ($ajax) { empty($conf['api_on']) and message(0, lang('closed')); $apilist['header'] = $header; $apilist['extra'] = $extra; $apilist['access'] = $access; $apilist['thread'] = well_thread_safe_info($thread); $apilist['thread_data'] = $data; $apilist['forum'] = $forum; $apilist['imagelist'] = $imagelist; $apilist['filelist'] = $thread['filelist']; $apilist['threadlist'] = $threadlist; message(0, $apilist); } else { include _include(theme_load('single_page', $fid)); } break; default: message(-1, lang('data_malformation')); break; } ?>