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

TypeScript移除对象数组重复项时,如何约束键对应值为string/number类型以解决类型错误

Great question! The core issue here is that TypeScript doesn’t automatically know T[K] will be a valid object key type (string/number/symbol) by default. Let’s fix this by adding precise type constraints while keeping your original logic completely intact.

Step 1: Restrict Valid Property Types with Generic Constraints

We need to ensure the value of T[K] is one of the allowed types for object keys. You can refine the K generic to only include keys of T where the corresponding value falls into string | number | symbol.

There are two clean ways to do this, depending on your TypeScript version:

Option 1: TypeScript 4.9+ (Using where Clause for Readability)

The where clause makes the constraint straightforward and easy to read:

function removeDuplicates<T, K extends keyof T where T[K] extends string | number | symbol>(arr: T[], propName: K) {
  const newArr: T[] = [];
  const lookup: Record<T[K], T> = {} as Record<T[K], T>; // Assert empty object as valid Record
  for (let i in arr) {
    lookup[arr[i][propName]] = arr[i];
  }
  for (let i in lookup) {
    newArr.push(lookup[i]);
  }
  return newArr;
}

Option 2: Compatible with Older TypeScript Versions

If you’re using a version before 4.9, use a conditional type to filter valid keys:

function removeDuplicates<T, K extends keyof T & (T[K] extends string | number | symbol ? K : never)>(arr: T[], propName: K) {
  const newArr: T[] = [];
  const lookup: Record<T[K], T> = {} as Record<T[K], T>;
  for (let i in arr) {
    lookup[arr[i][propName]] = arr[i];
  }
  for (let i in lookup) {
    newArr.push(lookup[i]);
  }
  return newArr;
}

Step 2: Fix the lookup Type

  • We replaced Partial<Record<T[K], T>> with Record<T[K], T>: Partial would make lookup values optional, which would cause errors when pushing to newArr. Since we’ll be populating the lookup dictionary, a full Record type is correct.
  • We use as Record<T[K], T> to assert the empty object {} is a valid starting point. This is safe because we immediately start adding entries to it in the loop.

How This Enforces Your Usage Rules

  • The generic constraint ensures developers can only pass a propName whose corresponding value is a valid object key. If they try to use a key with a value like object or array, TypeScript throws a compile-time error—exactly what you want to enforce your intended usage.
  • Your original logic stays identical; we’re just adding type safety to prevent invalid inputs.

Example Usage

Let’s test this with a sample type to see it in action:

interface User {
  id: number;
  username: string;
  preferences: { theme: string }; // This value is an object, so it can't be used as a key
}

const users: User[] = [
  { id: 1, username: "alice123", preferences: { theme: "dark" } },
  { id: 2, username: "bob456", preferences: { theme: "light" } },
  { id: 1, username: "alice_dup", preferences: { theme: "dark" } }
];

// Valid: Uses "id" (number, a valid key type)
const uniqueUsers = removeDuplicates(users, "id");

// Invalid: TypeScript throws an error, since "preferences" value is an object
removeDuplicates(users, "preferences");

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 12:13:10