Last active
December 26, 2022 05:14
-
-
Save emmaly/58e542a8b680655e9d767c103b0002e5 to your computer and use it in GitHub Desktop.
JavaScript type match helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"desc": "primative string", | |
"asString": "something", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:slice", | |
"has:constructor", | |
"string" | |
] | |
}, | |
{ | |
"desc": "String object, non-primative", | |
"asString": "something", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:slice", | |
"has:constructor", | |
"object", | |
"String" | |
] | |
}, | |
{ | |
"desc": "primative number", | |
"asString": "42", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"has:toFixed", | |
"number", | |
"number:valid" | |
] | |
}, | |
{ | |
"desc": "plain object", | |
"asString": "[object Object]", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"object", | |
"Object" | |
] | |
}, | |
{ | |
"desc": "plain object", | |
"asString": "[object Object]", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"object", | |
"Object" | |
] | |
}, | |
{ | |
"desc": "Number()", | |
"asString": "0", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"has:toFixed", | |
"number", | |
"number:valid" | |
] | |
}, | |
{ | |
"desc": "new Number()", | |
"asString": "0", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"has:toFixed", | |
"object", | |
"Number", | |
"Number:valid" | |
] | |
}, | |
{ | |
"desc": "Infinity (which is a special number)", | |
"asString": "Infinity", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"has:toFixed", | |
"number", | |
"number:valid", | |
"Infinity" | |
] | |
}, | |
{ | |
"desc": "NaN (which a special number, which is not a number)", | |
"asString": "NaN", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"has:toFixed", | |
"number", | |
"NaN" | |
] | |
}, | |
{ | |
"desc": "Date()", | |
"asString": "Sun Dec 25 2022 21:13:30 GMT-0800 (Pacific Standard Time)", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:slice", | |
"has:constructor", | |
"string" | |
] | |
}, | |
{ | |
"desc": "new Date()", | |
"asString": "Sun Dec 25 2022 21:13:30 GMT-0800 (Pacific Standard Time)", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"object", | |
"Date", | |
"Date:valid" | |
] | |
}, | |
{ | |
"desc": "String().toString (which is a native function)", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"function", | |
"function:native" | |
] | |
}, | |
{ | |
"desc": "isType (which is the function this is running through)", | |
"matches": [ | |
"has:toString", | |
"has:valueOf", | |
"has:constructor", | |
"function" | |
] | |
} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const testingAgainstTheseTypes = [ | |
"has:toString", | |
"has:valueOf", | |
"has:tacos", | |
"has:slice", | |
"has:constructor", | |
"has:toFixed", | |
"function", | |
"function:native", | |
"object", | |
"Object", | |
"string", | |
"String", | |
"Number", | |
"Number:valid", | |
"number", | |
"number:valid", | |
"Infinity", | |
"NaN", | |
"Date", | |
"Date:valid", | |
]; | |
const results = [ | |
["something", "primative string"], | |
[new String("something"), "String object, non-primative"], | |
[42, "primative number"], | |
[{}, "plain object"], | |
[new Object(), "plain object"], | |
[Number(), "Number()"], | |
[new Number(), "new Number()"], | |
[Infinity, "Infinity (which is a special number)"], | |
[NaN, "NaN (which a special number, which is not a number)"], | |
[Date(), "Date()"], | |
[new Date(), "new Date()"], | |
[String().toString, "String().toString (which is a native function)"], | |
[isType, "isType (which is the function this is running through)"], | |
] | |
.map(([o, desc]) => { | |
const out = {}; | |
out.desc = desc; | |
if (isType(o, "has:toString") && !isType(o, "function")) | |
out.asString = o.toString(); | |
out.matches = isType(o, testingAgainstTheseTypes, false, true); | |
return out; | |
}); | |
JSON.stringify(results, null, 2); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Returns `true` if `o` matches any of the `types`. | |
* `matchAll` modifies this behavior. | |
* `returnMatched` modifies it even more. | |
* | |
* @param {any} o | |
* The variable to test against. | |
* | |
* @param {TestableType|TestableType[]} types | |
* A type or list of types to test against. | |
* They can be a list of classes to match against as well. | |
* The list provided in `TestableTypes` is only a suggestion. | |
* | |
* @param {boolean} matchAll | |
* Changes the behavior to require that all `types` match against `o` | |
* in order to return `true`, rather than the default behavior of | |
* simply matching at least one of the provided `types`. | |
* | |
* Ignored if `returnMatched` is `true`. | |
* | |
* Default is `false`. | |
* | |
* @param {boolean} returnMatched | |
* Ignore what I said. If you set this `true`, it'll just give you | |
* an array of all matches. | |
* | |
* If you set this to `true` then `matchAll` is ignored altogether. | |
* | |
* Default is `false`. | |
* | |
* @returns {boolean|string[]} | |
* * If `returnMatched` is `false` (default), returns `boolean`. | |
* * If `returnMatched` is `true`, returns `string[]`. | |
*/ | |
function isType(o, types, matchAll, returnMatched) { | |
matchAll = matchAll === true; // sets to `false` if missing or wrong type | |
returnMatched = returnMatched === true; // sets to `false` if missing or wrong type | |
const matches = ( | |
Array.isArray(types) | |
? types // supply the already-ready array | |
: [types] // supply a new single-item array | |
).filter((type) => { | |
// `type` is supposed to be a string, but since it's not, | |
// perhaps we're being asked for an `instanceof` test... | |
if (typeof type === "object") return o instanceof type; | |
// get our base type | |
const baseType = typeof o; | |
// the most simple of tests: | |
// does the requested `type` match its base type? | |
if (type === baseType) return true; | |
// `number:valid`: a `number` & a validity test. | |
if (type === "number:valid") return (baseType === "number" && !isNaN(o)); | |
// `Number:valid`: a `Number` (not a `number`) & a validity test. | |
if (type === "Number:valid") return (baseType === "object" && o instanceof Number && !isNaN(o)); | |
// `Infinity`: a `number`, which is a special number that's somewhat large. | |
if (type === "Infinity") return (baseType === "number" && o === Infinity); | |
// `NaN`: a `number`, which a special number, which is not a number. | |
if (type === "NaN") return (baseType === "number" && isNaN(o)); | |
// `has:*` property test | |
if (typeof type === "string" && type.startsWith('has:')) | |
return o !== null && o !== undefined && o[type.replace(/^has:/, '')] !== undefined; | |
// only object and function baseTypes test beyond this point... | |
// ... so if it isn't one of those, let's bail now. | |
if (!["object","function"].includes(baseType)) return false; | |
// ~~ no primitive types (except null) beyond this point ~~ | |
// `null`: it's a special duck! | |
// It's a primitive... but also `typeof null === 'object'`. | |
if (type === "null") return (baseType === "object" && o === null); | |
// `Date`: an instance of `Date` is good enough. | |
if (type === "Date") return (baseType === "object" && o instanceof Date); | |
// `Date:valid`: an instance of `Date` & a validity test. | |
if (type === "Date:valid") return (baseType === "object" && o instanceof Date && !isNaN(o)); | |
// `Array`: is an `Array` | |
if (type === "Array") return (baseType === "object" && Array.isArray(o)); | |
// `class`: a `function` with source code starting with the word `class`. | |
if (type === "class") | |
return ( | |
baseType === "function" && | |
Function.prototype.toString.call(o).startsWith("class") | |
); | |
// `function:native`: a `function` with source code containing `native code` and no line breaks. | |
if (type === "function:native") | |
return ( | |
baseType === "function" && | |
/^\s*function\s*[^\n\{]+\{[^\n\{]+native code[^\n\}]+\}[^\n\}]*$/ | |
.test(Function.prototype.toString.call(o)) | |
); | |
// `type` === `className` perhaps... | |
const className = o.constructor.name; | |
if (type === className) return true; | |
// fail: no match | |
return false; | |
}); | |
// if returnMatched=true, just return matches | |
if (returnMatched === true) return matches; | |
// nothing matched always returns false | |
if (!matches || !Array.isArray(matches) || matches.length === 0) | |
return false; | |
return matchAll === true | |
? matches.length === types.length // ALL matches are required to give `true` | |
: matches.length > 0; // ANY matches are good enough for `true` | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// these are just suggestions and examples... | |
const TestableTypes = { | |
"undefined" : "undefined", | |
"null" : "null", | |
"object" : "object", | |
"Object" : "Object", | |
"boolean" : "boolean", | |
"Boolean" : "Boolean", | |
"number" : "number", | |
"number:valid" : "number:valid", | |
"Number" : "Number", | |
"Number:valid" : "Number:valid", | |
"NaN" : "NaN", | |
"Infinity" : "Infinity", | |
"bigint" : "bigint", | |
"BigInt64Array" : "BigInt64Array", | |
"BigUint64Array" : "BigUint64Array", | |
"string" : "string", | |
"String" : "String", | |
"symbol" : "symbol", | |
"function" : "function", | |
"function:native" : "function:native", | |
"Date" : "Date", | |
"Date:valid" : "Date:valid", | |
"Array" : "Array", | |
"Boolean" : "Boolean", | |
"class" : "class", | |
"has:toString" : "has:toString", | |
"has:toValue" : "has:toValue", | |
"has:constructor" : "has:constructor", | |
}; | |
/** | |
* @typedef {keyof TestableTypes} TestableType | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment