Javascript is required
踩坑避坑发布于 2026-07-28审校于 2026-08-084 分钟阅读

JSON 转 TypeScript:null、字段缺失、optional 与 union

JSON 中没有 undefined:字段可以完全缺失,也可以存在且值为 null。TypeScript 的 ? 表示属性可能不存在,string | null 表示属性存在时可以是 null,二者组合后仍与 undefined 不同。本文只讨论这四种状态的建模,不讨论 OpenAPI 代码生成。

JSON to TypeScriptNullOptionalUnion Types

一、问题概述:缺失和 null 不是同一种输入

{}{"nickname":null} 在 JSON 中不同。前者没有 nickname 键,后者明确传递空值;如果类型只写 nickname: string | null,缺失字段就不符合模型;只写 nickname?: string,又无法表达服务端明确返回 null。

二、最小复现:optional 与 nullable 的组合

下面的类型把三种情况分开:必需数字、必需但可为 null 的 nickname,以及可缺失且存在时可为 null 的 note。开启 strictNullChecks 后,编译器会要求调用方处理这些分支。

type Profile = {
  id: number;
  nickname: string | null;
  note?: string | null;
};

const explicitNull: Profile = { id: 1, nickname: null };
const missingOptional: Profile = { id: 2, nickname: "Ada" };
console.log(Object.hasOwn(explicitNull, "note")); // false
console.log(Object.hasOwn(missingOptional, "nickname")); // true

三、根因:TypeScript 属性访问会把缺失表现为 undefined

读取可选属性时,运行时结果可能是 undefined;读取必需的 string | null 属性时,结果只能在 string 与 null 之间。JSON.stringify 还会省略值为 undefined 的对象属性,因此把 undefined 当成 JSON null 会改变传输契约。

四、推荐方案:先定义传输状态,再写 TypeScript 类型

先列出字段是否必需、是否允许 null、是否允许其他类型。缺失与 null 都有业务意义时使用 field?: T | null,只允许缺失不允许 null 时使用 field?: T,必须出现但可为空时使用 field: T | null。不要用宽泛的 any 掩盖状态差异。

五、完整代码:显式区分缺失、null 与实际值

以下函数返回一个状态标签,使用 Object.hasOwn 区分缺失和显式 null。它不把空字符串、0 或 false 当成缺失。

type Input = { nickname?: string | null; count: number | null };
type FieldState = "missing" | "null" | "value";

function nicknameState(input: Input): FieldState {
  if (!Object.hasOwn(input, "nickname")) return "missing";
  return input.nickname === null ? "null" : "value";
}

console.log(nicknameState({ count: 0 })); // missing
console.log(nicknameState({ nickname: null, count: 0 })); // null
console.log(nicknameState({ nickname: "", count: 0 })); // value

六、常见错误方案

value || defaultValue 会把空字符串、0 和 false 当成缺失;用 value ?? defaultValue 虽然保留了空字符串,但仍会把显式 null 和缺失合并;把所有可选字段都写成 any 则失去编译期提示。

七、边界条件:嵌套字段、数组和 union 收窄

嵌套对象的父字段缺失时,不能直接读取子字段;数组元素也可能分别缺失或为 null。union 需要通过 typeof、字面量比较或自定义类型守卫收窄,不能只靠类型断言绕过检查。

八、如何验证接口契约

准备字段缺失、显式 null、空字符串、0、false 和正常值的 JSON 样本。分别检查 Object.hasOwn、运行时值、JSON.stringify 输出和 TypeScript 编译错误;若接口要求省略 null 字段,应在序列化层明确实现。

九、FAQ

问:field?: string 等于 field: string | undefined 吗?答:访问结果可能相似,但 optional 还描述了对象键是否存在,exactOptionalPropertyTypes 等配置会影响赋值规则。

问:null 和 undefined 可以互换吗?答:JSON 传输中 undefined 不会作为值出现,不能未经契约转换。

问:union 是否意味着所有类型都要接受?答:是,调用方必须在使用前通过控制流或类型守卫收窄。

十、总结

字段缺失、null、optional 和 union 分别描述存在性、空值和允许的类型集合。用 ?| null 和明确的类型守卫表达传输契约,保留 0、false、空字符串等真实值,并用边界 JSON 与 strictNullChecks 验证。

来源与延伸阅读

技术审校所依据的规范与权威参考资料。

相关文章

继续阅读

可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。

打开关联工具