Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fails on native functions, like Number or String (SOLUTION INCLUDED) #179

Open
TheJaredWilcurt opened this issue May 16, 2024 · 3 comments

Comments

@TheJaredWilcurt
Copy link

TheJaredWilcurt commented May 16, 2024

Steps to Reproduce:

const serializeJavaScript = require('serialize-javascript');

const input = {
  type: Number
};

const output = serializeJavaScript(input);

console.log(output);

Expected:

'{"type":Number}'

Actual:

Uncaught TypeError: Serializing native function: Number
    at serializeFunc (/node_modules/serialize-javascript/index.js:146:17)
    at /node_modules/serialize-javascript/index.js:266:16
    at String.replace (<anonymous>)
    at serialize (/node_modules/serialize-javascript/index.js:220:16)
@TheJaredWilcurt
Copy link
Author

TheJaredWilcurt commented May 16, 2024

Solution:

const x = Number;
console.log(x === Number); // true

console.log('' + Number); // 'function Number() { [native code] }'

Since you can check for native constructors, and can combine them with an empty string to get access to their name in a predictable position in a new string, a conditional formatter could be added:

const getNativeBuiltInName = function (value) {
  if (typeof(value) !== 'function') {
    return;
  }
  /**
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
   * Note: Not all global objects listed there can be combined when an empty string to
   * produce a native function string, needed in the following condition.
   */
  const nativeConstructorsAsStrings = [
    'AggregateError',
    'Array',
    'ArrayBuffer',
    'BigInt',
    'BigInt64Array',
    'BigUint64Array',
    'Boolean',
    'DataView',
    'Date',
    'Error',
    'EvalError',
    'FinalizationRegistry',
    'Float32Array',
    'Float64Array',
    'Function',
    'Int8Array',
    'Int16Array',
    'Int32Array',
    'Intl.Collator',
    'Intl.DateTimeFormat',
    'Intl.DisplayNames',
    'Intl.ListFormat',
    'Intl.Locale',
    'Intl.NumberFormat',
    'Intl.PluralRules',
    'Intl.RelativeTimeFormat',
    'Intl.Segmenter',
    'Iterator',
    'Map',
    'Number',
    'Object',
    'Promise',
    'Proxy',
    'RangeError',
    'ReferenceError',
    'RegExp',
    'Set',
    'SharedArrayBuffer',
    'String',
    'Symbol',
    'SyntaxError',
    'TypeError',
    'Uint8Array',
    'Uint8ClampedArray',
    'Uint16Array',
    'Uint32Array',
    'URIError',
    'WeakMap',
    'WeakRef',
    'WeakSet'
  ];
  const nativeConstructors = [];

  /**
   * Not all native constructors exist in all environments, some browsers or older Node
   * versions may not support everything. So here we check the window/global object
   * for their existence before pushing them onto the array for use in this function.
   * This prevents run-time bombs from popping up, and the need to meticulously document
   * the varied support of these features. However, to support both browsers and Node, we
   * use globalThis, which has wide support among Node/Browsers since 2019. But if you are
   * targeting legacy environments, you'd need to replace it with global or window.
   */
  for (const nativeConstructor of nativeConstructorsAsStrings) {
    if (globalThis[nativeConstructor]) {
      nativeConstructors.push(globalThis[nativeConstructor]);
    }
  }

  if (nativeConstructors.includes(value)) {
    // Number => 'function Number() { [native code] }' => 'Number'
    return (value + '').split(' ')[1].split('(')[0];
  }
  return;
};

With this all native constructors get turned into a string of their Name, and everything else is ignored and returned as undefined.

  • getNativeBuiltInName(Boolean) - returns 'Boolean'
  • getNativeBuiltInName(Number) - returns 'Number'
  • getNativeBuiltInName(String) - returns 'String'
  • getNativeBuiltInName(25) - returns undefined
  • getNativeBuiltInName('potato') - returns undefined

@TheJaredWilcurt TheJaredWilcurt changed the title Fails on native functions, like Number or String Fails on native functions, like Number or String (SOLUTION INCLUDED) May 19, 2024
@TheJaredWilcurt
Copy link
Author

If you prefer a harder to read, but marginally faster to execute regex approach for the last conditional:

if (nativeConstructors.includes(value)) {
  // Number => 'function Number() { [native code] }'
  const functionString = '' + value;
  const hasName = functionString.match(/function\s(\w+)\(\)/);
  if (hasName) {
    // 'Number'
    const name = hasName[1];
    return name;
  }
}

@TheJaredWilcurt
Copy link
Author

I'll leave this to @redonkulus to figure out the best way to implement this solution. Since this repo relies on Node specific stuff (Buffer), and my use case needs to work in a browser I just wrote my own serializer for my project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant