Skip to content
On this page

destr

JSON.parse 的更快、安全和方便的替代方案

  • 非字符串输入时会快速回退而不是 JSON.parse 报语法错误
    ts
    JSON.parse() //- Uncaught SyntaxError: Unexpected token u in JSON at position 0
    destr() //- undefined
  • 对已知字符串快速查找
    ts
    JSON.parse('TRUE') //- Uncaught SyntaxError: Unexpected token T in JSON at position 0
    destr('TRUE') //- true
  • 如果转换失败会回退到原始值(空或任何纯字符串)
    ts
    JSON.parse('salam') //- Uncaught SyntaxError: Unexpected token s in JSON at position 0
    destr('salam') //- 'salam'
  • 避免原型链污染, constructor__proto__ 会忽略
    ts
    const input = '{ "user": { "__proto__": { "isAdmin": true } } }'
    JSON.parse(input) //- { user: { __proto__: { isAdmin: true } } }
    destr(input) //- { user: {} }

严格模式

说明

如果第二个参数是对象且字段 stricttrue 时,当输入不是一个有效的 JSON 字符串或者转换失败,将会抛出错误,但非字符串和内置项仍然返回原始值

ts
//* 非严格模式
destr('[foo') //- Returns "[foo"
//* 严格模式
destr('[foo', { strict: true }) //- Throws an error

源码

ts
//* https://github.com/fastify/secure-json-parse
//* https://github.com/hapijs/bourne
const suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;

const JsonSigRx = /^\s*["[{]|^\s*-?\d[\d.]{0,14}\s*$/;

function jsonParseTransform (key: string, value: any): any {
  if (key === "__proto__") {
    return;
  }
  if (
    key === "constructor" &&
    value &&
    typeof value === "object" &&
    ("prototype" in value)
  ) {
    //* Has possible malicious prototype
    return;
  }
  return value;
}
export type Options = {
  strict?: boolean
}
export default function destr (value: any, options: Options = {}): any {
  //* 非字符串则直接返回原始值
  if (typeof value !== "string") {
    return value;
  }
  //* 小写且去除前后空字符,主要判断是否是特殊的内置项,如布尔值,null,nan等
  const _lval = value.toLowerCase().trim();
  if (_lval === "true") { return true; }
  if (_lval === "false") { return false; }
  if (_lval === "null") { return null; }
  //* Not a Number
  if (_lval === "nan") { return Number.NaN; }
  if (_lval === "infinity") { return Number.POSITIVE_INFINITY; }
  //* 这里应该也可以支持 Number.NEGATIVE_INFINITY
  if (_lval === "undefined") { return undefined; }
  //* 校验是否是JSON字符串
  if (!JsonSigRx.test(value)) {
    //* 不是JSON字符串且是严格模式,则抛出错误
    if (options.strict) {
      throw new SyntaxError("Invalid JSON");
    }
    //* 不是JSON字符串,返回原始值
    return value;
  }
  try {
    if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
      return JSON.parse(value, jsonParseTransform);
    }
    return JSON.parse(value);
  } catch (error) {
    //* 严格模式时抛出错误
    if (options.strict) {
      throw error;
    }
    //* 返回原始值
    return value;
  }
}

从整体逻辑上看,是相对简单的,但要抓细节和关键点.这里的关键点就是三个正则了。还有就是 JSON.parse 的第二个参数.

正则

  1. const JsonSigRx = /^\s*["[{]|^\s*-?\d[\d.]{0,14}\s*$/;

  2. const suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;

  3. const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;

JSON.parse

官方文档

第二个参数为函数时,在返回之前如何转换最初通过解析生成的每个值。将忽略不可调用的值。