jsonDump = (value, stack) => {
  let res;
  if (value === null) {
    res = 'null';
  } else if (typeof value === 'object') {
    if (!stack) {
      stack = [value]; // Creating the array here, only when type is object.
    } else if (stack::indexOf(value) >= 0) {
      throw new SafeError('Converting circular structure to JSON');
    } else {
      setOwnProp(stack, stack.length, value);
    }
    if (arrayIsArray(value)) {
      res = '[';
      // Must enumerate all values to include holes in sparse arrays
      for (let i = 0, len = value.length; i < len; i += 1) {
        res += `${i ? ',' : ''}${jsonDump(value[i], stack) ?? 'null'}`;
      }
      res += ']';
    } else {
      res = '{';
      objectKeys(value)::forEach((key) => {
        const v = jsonDump(value[key], stack);
        // JSON.stringify skips keys with `undefined` or incompatible values
        if (v !== undefined) {
          res += `${res.length > 1 ? ',' : ''}${jsonStringify(key)}:${v}`;
        }
      });
      res += '}';
    }
    stack.length -= 1;
  } else if (value !== undefined) {
    res = jsonStringify(value);
  }
  return res;
}
When running in the page context we must beware of sites that override Array#toJSON
leading to an invalid result, which is why our jsonDump() ignores toJSON.