Fails on native functions, like Number or String (SOLUTION INCLUDED)
TheJaredWilcurt opened this issue · 3 comments
TheJaredWilcurt commented
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 commented
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)
- returnsundefined
getNativeBuiltInName('potato')
- returnsundefined
TheJaredWilcurt commented
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 commented
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.