function stringify()

in packages/shared/util/Recoil_stableStringify.js [20:114]


function stringify(x: mixed, opt: Options, key?: string): string {
  // A optimization to avoid the more expensive JSON.stringify() for simple strings
  // This may lose protection for u2028 and u2029, though.
  if (typeof x === 'string' && !x.includes('"') && !x.includes('\\')) {
    return `"${x}"`;
  }

  // Handle primitive types
  switch (typeof x) {
    case 'undefined':
      return ''; // JSON.stringify(undefined) returns undefined, but we always want to return a string
    case 'boolean':
      return x ? 'true' : 'false';
    case 'number':
    case 'symbol':
      // case 'bigint': // BigInt is not supported in www
      return String(x);
    case 'string':
      // Add surrounding quotes and escape internal quotes
      return JSON.stringify(x);
    case 'function':
      if (opt?.allowFunctions !== true) {
        throw err('Attempt to serialize function in a Recoil cache key');
      }
      return `__FUNCTION(${x.name})__`;
  }

  if (x === null) {
    return 'null';
  }
  // Fallback case for unknown types
  if (typeof x !== 'object') {
    return JSON.stringify(x) ?? '';
  }

  // Deal with all promises as equivalent for now.
  if (isPromise(x)) {
    return '__PROMISE__';
  }

  // Arrays handle recursive stringification
  if (Array.isArray(x)) {
    return `[${x.map((v, i) => stringify(v, opt, i.toString()))}]`;
  }

  // If an object defines a toJSON() method, then use that to override the
  // serialization.  This matches the behavior of JSON.stringify().
  // Pass the key for compatibility.
  // Immutable.js collections define this method to allow us to serialize them.
  if (typeof x.toJSON === 'function') {
    // flowlint-next-line unclear-type: off
    return stringify((x: any).toJSON(key), opt, key);
  }

  // For built-in Maps, sort the keys in a stable order instead of the
  // default insertion order.  Support non-string keys.
  if (x instanceof Map) {
    const obj: {[string]: $FlowFixMe} = {};
    for (const [k, v] of x) {
      // Stringify will escape any nested quotes
      obj[typeof k === 'string' ? k : stringify(k, opt)] = v;
    }
    return stringify(obj, opt, key);
  }

  // For built-in Sets, sort the keys in a stable order instead of the
  // default insertion order.
  if (x instanceof Set) {
    return stringify(
      Array.from(x).sort((a, b) =>
        stringify(a, opt).localeCompare(stringify(b, opt)),
      ),
      opt,
      key,
    );
  }

  // Anything else that is iterable serialize as an Array.
  if (
    Symbol !== undefined &&
    x[Symbol.iterator] != null &&
    typeof x[Symbol.iterator] === 'function'
  ) {
    // flowlint-next-line unclear-type: off
    return stringify(Array.from((x: any)), opt, key);
  }

  // For all other Objects, sort the keys in a stable order.
  return `{${Object.keys(x)
    .filter(k => x[k] !== undefined)
    .sort()
    // stringify the key to add quotes and escape any nested slashes or quotes.
    .map(k => `${stringify(k, opt)}:${stringify(x[k], opt, k)}`)
    .join(',')}}`;
}