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

TypeScript严格模式下unknown类型转指定对象类型的问题

问题描述

在TypeScript严格模式下解析JSON得到unknown类型的值,定义了如下类型:

type Auth = {
  accessToken: string;
};

需要实现一个同时保证运行时校验和编译时类型安全的转换函数,写了以下代码:

type Auth = {
  accessToken: string;
};

function convert(value: unknown): Auth {
  if (typeof value != "object") {
    throw new Error(`Unexpected type: ${typeof value}`);
  }
  if (value == null) {
    throw new Error("Unexpected value: null");
  }
  if (!("accessToken" in value)) {
    throw new Error("Missing property: accessToken");
  }
  if (typeof value.accessToken != "string") {
    throw new Error(`Unexpected type: ${typeof value.accessToken}`);
  }
  return value;
}

但编译时出现错误:

Type 'object & Record<"accessToken", unknown>' is not assignable to type 'Auth'.
  Types of property 'accessToken' are incompatible.
    Type 'unknown' is not assignable to type 'string'.ts(2322)

不想用value as Auth强制断言,因为后续给Auth新增字段时,编译器没法提醒补充对应的校验逻辑,希望不绕过类型系统实现转换。

解决方案

方法一:自定义类型守卫函数

把校验逻辑抽成一个带类型谓词的函数,既能做运行时检查,又能让TypeScript正确收窄类型:

type Auth = {
  accessToken: string;
};

function isAuth(value: unknown): value is Auth {
  if (typeof value !== "object" || value === null) {
    return false;
  }
  const authCandidate = value as Record<string, unknown>;
  return "accessToken" in authCandidate && typeof authCandidate.accessToken === "string";
}

function convert(value: unknown): Auth {
  if (!isAuth(value)) {
    throw new Error("Invalid Auth structure");
  }
  return value;
}

这里的value is Auth类型谓词会告诉TypeScript:如果isAuth返回true,那么value一定符合Auth类型。后续如果Auth新增字段,比如加refreshToken: string,修改isAuth时必须同步添加对应校验,否则类型系统会在使用转换后的Auth对象时保持安全,而且你也能明确知道要补充检查逻辑。

方法二:函数内逐步收窄类型

不想拆分函数的话,可以用临时变量辅助TypeScript识别属性,再通过校验逐步收窄类型:

type Auth = {
  accessToken: string;
};

function convert(value: unknown): Auth {
  if (typeof value !== "object" || value === null) {
    throw new Error(`Unexpected type: ${typeof value}`);
  }
  // 先把value断言为包含可选accessToken的对象,辅助TS识别属性
  const candidate = value as { accessToken?: unknown };
  if (!("accessToken" in candidate)) {
    throw new Error("Missing property: accessToken");
  }
  if (typeof candidate.accessToken !== "string") {
    throw new Error(`Unexpected type for accessToken: ${typeof candidate.accessToken}`);
  }
  // 此时candidate的结构完全匹配Auth,断言是安全的
  return candidate as Auth;
}

这种方式下,后续如果Auth新增字段,比如refreshToken: string,返回时TypeScript会因为candidate没有这个属性而报错,你就能立刻知道要添加对应的检查逻辑,不会漏掉校验。

方法三:使用断言函数(TS 3.7+)

TypeScript 3.7及以上支持断言函数,可以直接在函数内告诉编译器类型:

type Auth = {
  accessToken: string;
};

// 断言函数:如果不抛出错误,value就是Auth类型
function assertIsAuth(value: unknown): asserts value is Auth {
  if (typeof value !== "object" || value === null) {
    throw new Error(`Unexpected type: ${typeof value}`);
  }
  if (!("accessToken" in value)) {
    throw new Error("Missing property: accessToken");
  }
  if (typeof value.accessToken !== "string") {
    throw new Error(`Unexpected type for accessToken: ${typeof value.accessToken}`);
  }
}

function convert(value: unknown): Auth {
  assertIsAuth(value);
  return value;
}

断言函数的语义更明确,专门用于“校验不通过就抛错”的场景。后续修改Auth类型时,必须同步更新assertIsAuth里的校验逻辑,否则编译器会在使用value为Auth类型时检测到不匹配,提醒你补充检查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 08:05:24