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

TypeScript:实现带动态输出类型的对象属性提取及通用接口

动态类型安全的toJSON方法实现方案

需求与问题

我有多个类,每个类都具备toJSON方法用于将实例转换为JSON格式。现在需要为这些方法新增一个可选参数,使其能够仅返回类属性的子集,并且返回类型要根据开发者选择的属性动态变化,保证类型安全。

当前方案存在类型漏洞:当传入attributes: ['name']时,返回的truncatedUser仍能访问id/age属性,不会触发TS编译错误,不符合预期。

预期效果示例:

interface IUser {
  id: string;
  name: string;
  age: number;
}

const fullUser = user.toJSON(); // 类型为完整IUser
const truncatedUser = user.toJSON({ attributes: ['name'] }); // 类型为Pick<IUser, 'name'>

fullUser.id; // 正常可用
truncatedUser.id; // 触发TS编译错误

现有实现代码

// 使用泛型以适配IUser、IABC、IDEF等不同接口
interface IJSONExportOptions<Type> {
  /** 结果JSON中包含的对象属性列表 */
  attributes?: (keyof Type)[];
}

interface IUser {
  id: string;
  name: string;
  age: number;
}

class User {
  private id: string;
  private name: string;
  private age: number;

  constructor() {
    this.id = '1';
    this.name = 'Jon Doe';
    this.age = 20;
  }

  toJSON(opts?: IJSONExportOptions<IUser>) {
    const fullObj: IUser = {
      id: this.id,
      name: this.name,
      age: this.age,
    };

    if (!opts?.attributes?.length) {
      return fullObj as Pick<IUser, keyof IUser>;
    }

    const truncatedUser: Partial<Pick<IUser, keyof IUser>> = {};
    for (const key of opts?.attributes) {
      const val = fullObj[key];
      truncatedUser[key] = val;
    }
    return truncatedUser as Pick<IUser, keyof IUser>;
  }
}

const user = new User()
const fullUser = user.toJSON();

console.log(fullUser.age);

const truncatedUser = user.toJSON({ attributes: ['name'] });

console.log(truncatedUser.age); // 此处未触发预期的TS编译错误

解决方案代码

// 调整泛型选项,让属性列表绑定具体的键类型
interface IJSONExportOptions<Type, K extends keyof Type = keyof Type> {
  attributes?: K[];
}

interface IUser {
  id: string;
  name: string;
  age: number;
}

class User {
  private id: string;
  private name: string;
  private age: number;

  constructor() {
    this.id = '1';
    this.name = 'Jon Doe';
    this.age = 20;
  }

  // 给toJSON方法添加泛型K,绑定传入的属性键
  toJSON<K extends keyof IUser = keyof IUser>(opts?: IJSONExportOptions<IUser, K>): K extends keyof IUser ? Pick<IUser, K> : IUser {
    const fullObj: IUser = {
      id: this.id,
      name: this.name,
      age: this.age,
    };

    // 未传入属性列表时返回完整对象
    if (!opts?.attributes?.length) {
      return fullObj as ReturnType<typeof this.toJSON>;
    }

    // 断言为Pick类型,确保类型匹配
    const truncatedUser = {} as Pick<IUser, K>;
    for (const key of opts.attributes) {
      truncatedUser[key] = fullObj[key];
    }
    return truncatedUser as ReturnType<typeof this.toJSON>;
  }
}

const user = new User()
const fullUser = user.toJSON();
console.log(fullUser.age); // 正常,类型为IUser

const truncatedUser = user.toJSON({ attributes: ['name'] });
console.log(truncatedUser.age); // 触发TS编译错误:类型“Pick<IUser, "name">”上不存在属性“age”

// 支持多属性选择
const partialUser = user.toJSON({ attributes: ['id', 'age'] });
console.log(partialUser.name); // 同样触发编译错误

关键改动说明

  1. 泛型绑定属性键:给IJSONExportOptions和toJSON方法添加泛型K,让attributes的类型从(keyof Type)[]变为具体的K[],TS能精准追踪选中的属性。
  2. 动态返回类型:通过条件类型K extends keyof IUser ? Pick<IUser, K> : IUser,让返回类型根据传入的attributes自动切换:
    • 未传参数时,K默认是keyof IUser,返回完整IUser类型
    • 传入属性列表时,返回对应的Pick<IUser, K>类型
  3. 类型断言优化:将truncatedUser直接断言为Pick<IUser, K>,避免之前Partial带来的类型模糊问题,确保返回对象只包含选中的属性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 18:33:16