功能说明
该示例把 JSON.parse 改造成返回 Result 的安全接口。解析成功时返回 ok 为 true 与值,失败时返回 ok 为 false 与结构化错误,调用方无需 try/catch 也不会漏掉异常。parseJsonWith 进一步接收类型谓词,在语法正确但结构不符合预期时返回 validation 错误,适合处理来自网络、文件或消息队列的不可信数据。
关键代码
Result 是可辨识联合,成功分支带 value,失败分支带 error,TypeScript 能根据 ok 自动收窄。parseJson 捕获 JSON.parse 的异常并把原因归为 syntax。parseJsonWith 先调用 parseJson 得到 unknown,再用传入的类型谓词检查;谓词不通过时返回 validation,通过后把 unknown 收窄为泛型 Value。示例 isUser 检查对象非空且 id、name 都是字符串。
使用注意
parseJson 的泛型断言不会做任何运行时校验,只有 parseJsonWith 才安全。类型谓词必须真正检查所有关键字段,不能只判断一个字段就返回 true。JSON.parse 不接受注释、尾逗号与 BigInt,超大文本也会占用大量内存,必要时先限制长度。解析出的对象仍可能包含 proto 等键,后续合并时要使用安全策略。
验证方式
断言合法用户 JSON 返回 ok 且能读取 name,语法错误返回 reason 为 syntax,字段类型错误返回 reason 为 validation。测试顶层数组、null 与缺少字段的对象都被谓词拒绝。传入非函数谓词断言 TypeError,并确认所有失败分支都没有抛出异常。
代码示例
export type Result<Value, Failure> =
| { readonly ok: true; readonly value: Value }
| { readonly ok: false; readonly error: Failure };
export type JsonParseError =
| { readonly reason: "syntax"; readonly message: string }
| { readonly reason: "validation"; readonly message: string };
export function parseJson<Value = unknown>(text: string): Result<Value, JsonParseError> {
if (typeof text !== "string") throw new TypeError("text must be a string");
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch (error) {
return {
ok: false,
error: { reason: "syntax", message: error instanceof Error ? error.message : String(error) },
};
}
return { ok: true, value: parsed as Value };
}
export function parseJsonWith<Value>(
text: string,
guard: (value: unknown) => value is Value,
): Result<Value, JsonParseError> {
if (typeof guard !== "function") throw new TypeError("guard must be a type predicate function");
const result = parseJson<unknown>(text);
if (!result.ok) return result;
if (!guard(result.value)) {
return {
ok: false,
error: { reason: "validation", message: "value did not match the expected shape" },
};
}
return { ok: true, value: result.value };
}
interface User {
readonly id: string;
readonly name: string;
}
function isUser(value: unknown): value is User {
if (value === null || typeof value !== "object") return false;
const record = value as Record<string, unknown>;
return typeof record.id === "string" && typeof record.name === "string";
}
const valid = parseJsonWith('{"id":"u1","name":"Ada"}', isUser);
if (valid.ok) console.log(valid.value.name);
else console.log(valid.error.reason);
const invalid = parseJsonWith("{oops}", isUser);
console.log(invalid);
const wrongShape = parseJsonWith('{"id":1}', isUser);
console.log(wrongShape);