You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从实现[string]索引接口的对象动态获取键联合类型?

How to Get Literal Union Type from Object Keys in TypeScript

Problem Breakdown

You’ve set up an interface PRODUCT_CATEGORIES_TYPE to constrain your PRODUCT_CATEGORIES object, but when using keyof typeof PRODUCT_CATEGORIES, you’re getting string | number instead of the expected literal union like "category1" | "category2". You’re also curious if manually defining this union type is a valid approach, and want to know a better solution.

The core issue here is type widening: when you explicitly annotate PRODUCT_CATEGORIES with your interface, TypeScript automatically widens the object’s keys from specific string literals to the general string type. That’s why keyof returns a broad string | number instead of your desired exact key union.


Solution 1: Use as const to Preserve Literal Types

The simplest fix leverages TypeScript’s const assertion, which tells the compiler to keep all types as exact literals instead of widening them.

Update CATEGORIES.ts

Remove the explicit interface annotation and add as const to your object:

// CATEGORIES.ts
export const PRODUCT_CATEGORIES = {
  category1: { EN: { label: "Label english" }, ES: { label: "Label spanish" } },
  category2: { EN: { label: "Label english" }, ES: { label: "Label spanish" } },
  // More categories...
} as const;

Derive Your Types

Now you can generate the exact types you need directly from the inferred type of the object:

// Full type of the categories object
export type PRODUCT_CATEGORIES_TYPE = typeof PRODUCT_CATEGORIES;

// Union of all category keys (e.g., "category1" | "category2")
export type CATEGORY_KEYS = keyof PRODUCT_CATEGORIES_TYPE;

Use the Union in Your PRODUCT Interface

interface PRODUCT {
  category: CATEGORY_KEYS; // Exact literal union type
}

Solution 2: Generic Factory Function (For Structure Enforcement)

If you still want to enforce that each category follows your language-label structure, use a generic function to constrain the object while preserving literal types:

Define the Factory Function

// Assuming LANGUAGE is a pre-defined union like "EN" | "ES"
type LANGUAGE = "EN" | "ES";

function createProductCategories<T extends Record<string, Record<LANGUAGE, { label: string }>>>(
  categories: T
) {
  return categories;
}

Initialize Categories with the Function

// CATEGORIES.ts
export const PRODUCT_CATEGORIES = createProductCategories({
  category1: { EN: { label: "Label english" }, ES: { label: "Label spanish" } },
  category2: { EN: { label: "Label english" }, ES: { label: "Label spanish" } },
});

Derive the Key Union Type

Same as before, get your exact union via keyof:

export type CATEGORY_KEYS = keyof typeof PRODUCT_CATEGORIES;

Is Manual Union Definition Valid?

Manually writing CATEGORY_VALUES = "category1" | "category2" works for small, rarely changing sets of categories, but it’s not optimal:

  • Pros: It’s simple for static, short lists.
  • Cons: It creates a duplicate source of truth. If you add/remove a category in PRODUCT_CATEGORIES, you have to remember to update the union type too—this is error-prone and breaks the DRY (Don’t Repeat Yourself) principle.

The auto-inferred solutions above are far better because they keep your type definitions in sync with your runtime data automatically, eliminating manual maintenance.

内容的提问来源于stack exchange,提问作者cbdeveloper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 21:52:40