npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

typeanalyser

v2.0.6

Published

Provides type detection and analysis for ANY Javascript object (including custom types) in any environment, even where native operators falter.

Downloads

12

Readme

TypeAnalyser

Functions Branches GitHub npm npm bundle size

What is TypeAnalyser?


A simple library designed to provide precise and in-depth type information for any JavaScript/Typescript object including the cases where the built-in javascript operators fail or provide misleading results.

Uniquely compared to other packages, it also provides additional type detection functionality such as checking for number and JSON type safety.

Why Use TypeAnalyser?


This package analyzes all types including your own custom classes. it provides a set of functions that fix and go beyond the native typeof, instanceof, and isNaN operators. Additionally it works correctly in all environments unlike the native operators.

Features :

| function | description | | ----------- | ------------------------- | | getTypeOf | Accurately identifies the actual type of any JavaScript object and it works consistently across iFrames, Worker threads and with DOM objects | | isSafeNum | Tests if a number is safe to use in calculations | | getNumTypeOf | Provides information on the specific subtype of a number | | getTypeDetails | Offers detailed information about the object, including its prototype chain | | isJSONSerializable | Checks if an object is correctly JSON serializable | | hasCircularRef | Checks for circular references |

Additionally, TypeAnalyser:

  • works correctly when using ES6 type polyfills in non-ES6 environments.

  • Provides ESM, CSJ, and IIFE module variants for various use cases (refer to the installation section below).

  • has zero-dependency and is lightweight, at just 15KB unminified and 3KB minified.

  • has been extensively tested across different browsers and runtime environments ( see development section below )

Usage

Here are descriptions along with some examples of TypeAnalyser usage.

getTypeOf( object )


Accurately identifies the type of all Javascript objects. It provides the same core functionality as the built-in Javascript 'typeof' operator but also has the following advantages. It;

  • returns the correct type for null, Array, all ES6 / ES2020 types and custom types ( E.g. your classes ).

  • works correctly with types correctly simulated via Polyfills (E.g. Symbol via Babel )

  • uniquely distinquishes between different types of functions( regular, async, generator, arrow )

  • correctly identifies types retieved from Iframes and Worker threads where passing of those types is supported.

Parameters
object - The object to determine the 'type' of

Returns
This function returns a string representing the type of the object passed in. if a type can't be determined the string unknown will be returned. The following types will be in lower case as per the built-in javascript typeof operator:

'string', 'number', 'boolean', 'undefined', 'symbol', 'function', 'object', 'bigint'.

All other built-in types will be recognised and returned in CamelCase format as per the Javascript standard: E.g. 'Array', 'Date', 'Error', 'RegExp', 'URL' etc. Here are some typical return values:

| Type | getTypeOf Output | | ----------- | ------------------------- | | Number | 'number' | | BigInt | 'bigint' | | String | 'string' | | Boolean | 'boolean' | | Symbol | 'symbol' | | Undefined | 'undefined' | | Null | 'null' | | Object | 'object' | | Function | 'function' | | Array | 'Array' | | Date | 'Date' | | RegExp | 'RegExp' | | Error | 'Error' | | TypeError | 'TypeError' | | Map | 'Map' | | WeakMap | 'WeakMap' | | Set | 'Set' | | WeakSet | 'WeakSet' | | Promise | 'Promise' | | Proxy | 'Proxy' | | Int8Array | 'Int8Array' | | Uint8Array | 'Uint8Array' | | SharedArrayBuffer | 'SharedArrayBuffer' | | URL | 'URL' | | URLSearchParams | 'URLSearchParams' | | Blob | 'Blob' | | async function | 'AsyncFunction' | | function* | 'GeneratorFunction' | | () => {} | 'ArrowFunction' | | window | 'Window' | | html | 'HTMLHtmlElement' | | canvas | 'HTMLCanvasElement' | | button | 'HTMLButtonElement' |

Examples

Working with null and object
Using typeof, you might try to access a property or method of an object which is actually null, leading to a TypeError. Using getTypeOf avoids this.

const ta = require('typeanalyser'); 

let x = null;
if (typeof x === "object") {        // typeof always returns 'object' for null
    x.property = "value";           // TypeError: Cannot set property 'property' of null
}
if (ta.getTypeOf(x) === "null") {
    console.log( "x is null, can't access properties or methods");  // Safe operation
}

let y = {};
if (ta.getTypeOf(y) === "object") {
    console.log( "y is an object, we can access it's properties or methods");  // Safe operation
    y.property = "value";
}

Note that while instanceof can be used as an alternative to typeof in some cases, it won't work reliably across iframes or Worker threads. Moreover, instanceof can yield false positives if there's a matching type in the object's prototype chain. Using getTypeOf will avoid these issues.

Identifying arrays

Arrays have unique methods that objects don't have. Trying to use these methods on an object will cause a TypeError. Using Array.isArray does not work in all environments (E.g. IFrames or Worker threads). Using getTypeOf all arrays including ES6 typed arrays can also be identified in all environments.

console.log( typeof someArrayInstance );                 // => 'object' not 'Array' - typeof considers Arrays as objects  

// This works across iframes and worker threads unlike Array.isArray( )
if (ta.getTypeOf( someArrayInstance ) === "Array") {  
    someArrayInstance.push(4); // Safe operation,
}

// All typed arrays can be recognised.  E.g. 
console.log( ta.getTypeOf(new Uint8Array) );       // => 'Uint8Array'
console.log( ta.getTypeOf(new Float32Array) );     // => 'Float32Array'
console.log( ta.getTypeOf(new BigInt64Array) );    // => 'BigInt64Array'

Detecting safe use of function types
There are sometimes cases where checking of function type should be done in order to avoid subtle run-time errors

function myFunction() {}
// check if it's safe to use call() or Apply( ) 
if (ta.getTypeOf(someFunction) === 'ArrowFunction') {
    throw new TypeError('Arrow functions cannot be invoked with new context using call() or apply().');
} else {
    someFunction.call( thisContext );
}

// check if it's safe to iterate 
if (ta.getTypeOf(someFunction) === 'GeneratorFunction') {
    const generator = someFunction();
    for (let value of generator) {
        console.log(value);
    }
} else {
    throw new TypeError('Expected a Generator Function.');
}

// check for safe use of a promise or something which returns a promise
if (ta.getTypeOf(someFunction) === 'Promise') {
    someFunction
        .then(result => console.log(result))
        .catch(error => console.error(error));
} else {
    let returnedPromise = someFunction();
    if (ta.getTypeOf(returnedPromise) === 'Promise') {
        returnedPromise()
            .then(result => console.log(result))
            .catch(error => console.error(error));
    } else {
        throw new TypeError('Expected a Promise or a function that returns a Promise.');
    }
}

API Data Handling:
When building applications, you often fetch data from external APIs, and this data can come in various forms. Being able to accurately determine the type of the received data is crucial for handling it correctly.

async function fetchData(url) {
  const response = await fetch(url);
  const data = await response.json();

  if (ta.getTypeOf(data) === 'Array') {
    data.forEach(item => {
      console.log(item);
    });
  } else if (ta.getTypeOf(data) === 'Object') {
    Object.keys(data).forEach(key => {
      console.log(`${key}: ${data[key]}`);
    });
  } else {
    console.error('Unexpected data type received from API');
  }
}

Building a Middleware for a Web Server:
When you're building middleware for a web server, you may receive data in the request body in different formats depending on the client's request. getTypeOf can also be useful here. In this example, expressApp is an instance of an Express.js application;

expressApp.use((req, res, next) => {
  if (ta.getTypeOf(req.body) === 'Array') {
    // Do something with the array
  } else if (ta.getTypeOf(req.body) === 'Object') {
    // Do something with the object
  } else {
    // Unexpected type - handle error
  }
  next();
});

checking an object type from an iframe or worker thread

const iframe = document.createElement("iframe");
iframe.src = "https://example.com/iframe.html";
iframe.onload = () => {
  const obj = iframe.contentWindow.obj;
  if (ta.getTypeOf(obj) === "MyCustomType") {
    // do something with the obj
    // This would not cause an error because getTypeOf obj is "MyCustomType"
    // using the builtin typeof wound cause an error because typeof obj is "object"
  }
};

Differentiating between 'Date', 'RegExp', and generic 'objects' You may want to perform operations specific to Date or RegExp but typeof treats them as generic objects which can lead to incorrect operations

let z = new Date();
console.log( typeof(z)  );                          // => 'object' not Date
if (ta.getTypeOf(z) === "Date") {
    console.log( z.getFullYear());                  // '2023' - Safe operation, outputs the year of date
}

let regex = new RegExp("\\d+");
console.log( typeof(regex) );                       // => 'object' not RegExp
if (ta.getTypeOf(regex) === "RegExp") {
    console.log( regex.test("123"));                // 'true' - Safe operation, tests if string contains a number
}

Working with ES6 and ES2020 types like 'Map' and 'Set' Map and Set have unique methods. Attempting these on a generic object will lead to errors.

let map = new Map();
if (ta.getTypeOf(map) === "Map") {
    map.set("key", "value");  // Safe operation, adds a key-value pair to the map
}

let set = new Set();
if (ta.getTypeOf(set) === "Set") {
    set.add("value");  // Safe operation, adds a value to the set
}

// All known ES6 types can be detected. E.g. 
const buffer = new ArrayBuffer(16);
console.log( ta.getTypeOf(new DataView(buffer)));          // => 'DataView'
console.log( ta.getTypeOf(buffer));                        // => 'ArrayBuffer'
console.log( ta.getTypeOf(new SharedArrayBuffer(16)));     // => 'SharedArrayBuffer'

Working with custom class instances

With custom class instances, you might want to use specific methods from the class. Using typeof might cause you to use methods that don't exist. Using instanceof works but not in all cases and can give false positives.

class MyClass { 
    printHello() { 
        console.log( "Hello, World!"); 
    }
};
let instance = new MyClass();

if (ta.getTypeOf(instance) === "MyClass") {
    instance.printHello();  // Safe operation in all environments, prints "Hello, World!"
}

Working with overridden Custom Tags and objects with toString( ) Method

let theObj = {
    name: 'John',
    age: 30,
    [Symbol.toStringTag]: 'SomeCustomTag'
}
// Setting [Symbol.toStringTag] allows the detected 'type' of custom objects to be defined
// so that objects can be distinquished from each other
console.log( ta.getTypeOf(theObj));                               // => 'MyCustomTag'

let myObj = {
    name: 'John',
    age: 30,
    toString: function() {
        return 'MagicObj';
    }
}
console.log( ta.getTypeOf(myObj));                                // => 'unknown' - by deisgn see below

class MyClass {
constructor() {
    this.name = 'John';
    this.age = 30;
}
get [Symbol.toStringTag]() {
    return 'Not_MyClass';
}   
let myClassInstance = new MyClass();
console.log( ta.getTypeOf(myClassInstance) );                    // => 'MyClass'  - by design see below
console.log( ta.myClassInstance[Symbol.toStringTag] );                // => 'Not_MyClass" - default javascript behavior still OK

Special Cases: toString( ) Override and [Symbol.toStringTag]

getTypeOf handles several special cases where the returned 'type' might not align with your expectations:

  • Objects with toString() Overridden:* The built-in toString() method, which all objects have, is essential to determining an object's type. If this is overridden by custom code, the ability to correctly determine the object's type is compromised therefore getTypeOf returns unknown in these cases. You can still use toString() to retrieve the value set by the custom code.

  • Custom Classes with [Symbol.toStringTag]:* If custom classes have [Symbol.toStringTag] set, TypeAnalyser will return the class name rather than the toStringTag value. This design is intentional. If you want to retrieve the custom tag, [Symbol.toStringTag] will still return whatever value was set.

  • Object Types with [Symbol.toStringTag]:* Actual Object types with [Symbol.toStringTag] set will have that value returned for the type of the object

Rationale
The goal of TypeAnalyser is to reveal the intrinsic underlying 'type' of an object. For built-in types and custom objects, using [Symbol.toStringTag] doesn't alter that. However, we consider actual Object types an exception and return the [Symbol.toStringTag] value. This is because JavaScript's type system returns 'object' for all Objects, making it impossible to distinguish one type of custom Object from another without using [Symbol.toStringTag].

Note that [Symbol.toStringTag] and toString() values are always accessible to you. TypeAnalyser simply provides you with additional type information you wouldn't otherwise have.

getNumTypeOf( object , acceptStringNums = true )


Returns information about the 'sub-type' of number passed in. Unlike the built-in javascript isNaN( ) function, this returns useful information about the 'type' of number passed in. E.g. it will return 'infinity' for Infinity or it will return 'unsafeNumber' for a number that is too large to be a safe integer. It will return 'NaN' for BigInt and Symbol types unlike the built-in isNaN( ) function which throws a type error for these types.

Also note
1 . NaN returns 'NaN' as that is the 'number' sub-type that it actually is.
2 . Booleans also returns 'NaN' because even though javascript coerces them into 0 or 1, relying on this behaviour is not good practice and can lead to bugs.
3 . Number Objects (created via new Number(xxx)) return 'numberObject'. Some JavaScript methods and functions behave differently when they receive an object instead of a primitive value. E.g. when comparing with ===, new Number(10) !== 10.

Parameters
object - The object to check to determine if it is a safe number

acceptStringNums - Optional. If true (the default value), then if a string is passed in, it will be converted to a number and the that will tested. Strings are not coerceced to numbers if they do not represent a valid number. E.g '34.345abchs' will not be converted to a number and will return 'NaN'. But '34.345' will be converted to a number and will return 'safeFloat'. Strings representing Hex numbers also work - E.g. 0xFF.

(Note - the built in javascript parseFloat() function can be used before calling this function to force coercing. E.g. it will convert '34.345abchs' to 34.345). if acceptStringNums is false then when a string is passed in, it will never be converted to a number and 'NaN' will be returned.

Returns
A string representing the sub-type of the number passed in. The possible values are: 'bigint', 'NaN' , 'infinity', '-infinity', 'safeInteger', 'unsafeNumber', 'safeFloat' and 'numberObject'.

Examples

// for Clarity assume ESM import 
import { getNumTypeOf } from './node_module/TypeAnalyser/dist/type-analyser.esm.min.js';

// non number types  
console.log( ta.getNumTypeOf(NaN) );               // => 'NaN' 
console.log( ta.getNumTypeOf('not a number') );    // => 'NaN'
console.log( ta.getNumTypeOf(true) );              // => 'NaN'
console.log( ta.getNumTypeOf(null) );              // => 'NaN'
console.log( ta.getNumTypeOf({}) );                // => 'NaN'
console.log( ta.getNumTypeOf(Symbol('hello')) );   // => 'NaN'
console.log( ta.getNumTypeOf(undefined) );         // => 'NaN'
console.log( ta.getNumTypeOf(function() {}) );     // => 'NaN'
console.log( ta.getNumTypeOf([]) );                // => 'NaN'

// Infinity 
console.log( ta.getNumTypeOf(1/0) );               // => 'infinity'
console.log( ta.getNumTypeOf(-1/0) );              // => '-infinity'

// safe integers 
var maxInt = Number.MAX_SAFE_INTEGER; 
console.log( ta.getNumTypeOf(10) );                // => 'safeInteger'
console.log( ta.getNumTypeOf(maxInt) );            // => 'safeInteger'
console.log( ta.getNumTypeOf(-maxInt) );           // => 'safeInteger'

// unsafe numbers to use in calculations
var maxInt = Number.MAX_SAFE_INTEGER; 
console.log( ta.getNumTypeOf(maxInt + 1) );            // => 'unsafeNumber'
console.log( ta.getNumTypeOf(-maxInt - 1) );           // => 'unsafeNumber'
console.log( ta.getNumTypeOf(Number.MAX_VALUE) );      // => 'unsafeNumber'
console.log( ta.getNumTypeOf(90071992547409975.33) );  // => 'unsafeNumber'

// safe floating point numbers' 
console.log( ta.getNumTypeOf(1.1) );                   // => 'safeFloat'
console.log( ta.getNumTypeOf(900719925474099.75) );    // => 'safeFloat'

// bigint' 
console.log( ta.getNumTypeOf(BigInt(123)) );       // => 'bigint'

// bigint literal' 
console.log( ta.getNumTypeOf( 123n ) );            // => 'bigint'

// numbers in string format 
console.log( ta.getNumTypeOf('123') );             // => 'safeInteger'
console.log( ta.getNumTypeOf('1.11') );            // => 'safeFloat'
console.log( ta.getNumTypeOf('-1.11') );           // => 'safeFloat'
console.log( ta.getNumTypeOf('0xFF') );            // => 'safeInteger'

// invalid numbers in string format
console.log( ta.getNumTypeOf('123xyz') );          // => 'NaN'
console.log( ta.getNumTypeOf('xyz') );             // => 'NaN'
console.log( ta.getNumTypeOf('123pqr', false) );   // => 'NaN'
console.log( ta.getNumTypeOf('zyx',false) );       // => 'NaN'

// numbers in string format with 'acceptStringNumber' param set to false' 
console.log( ta.getNumTypeOf('123', false) );      // => 'NaN'
console.log( ta.getNumTypeOf('123.99', false) );   // => 'NaN'

console.log( ta.getNumTypeOf( new Number(999)));   // => 'numberObject'  - not a number primitive

isSafeNum( object , acceptStringNums = true )


Tests to see if the number passed in is safe to use in a calculation. This is useful because Javascript has a number of different types of numbers and some of them are not safe to use in calculations. E.g. BigInts are not safe to use in calculations with regular numbers. Also, numbers that are too large to be safe integers are not safe to use in calculations.

Note;
1 . NaN returns false as it is not a safe number to use in calculations.
2 . Booleans also returns false because even though javascript coerces them into 0 or 1, relying on this behaviour is not good practice and can lead to bugs.
3 . Number Objects (created via new Number(xxx)) return false. Some JavaScript methods and functions behave differently when they receive an object instead of a primitive value. E.g. when comparing with ===, new Number(10) !== 10.

Parameters
object - The object to check to determine if it is a safe number

acceptStringNums - Optional. If true (the default value), then if a string is passed in, it will be converted to a number and it's type will tested for safe use. Strings are not coerceced to numbers if they do not represent a valid number. E.g '34.345abchs' will not be converted to a number and will return false. But '34.345' will be converted to a number and will return true. String representing Hex numbers also work - E.g. 0xFF.

(Note - the built in javascript parseFloat() function can be used before calling this function to force coercing. E.g. it will convert '34.345abchs' to 34.345). if acceptStringNums is false then when a string is passed in, it will never be converted to a number and false will be returned

Returns
true if the number passed in is safe to use in a calculation, false otherwise.

Examples

const ta = require('typeanalyser');

//safe integers
console.log( ta.isSafeNum(1) );                         // => true 
console.log( ta.isSafeNum(Number.MAX_SAFE_INTEGER) );   // => true 
console.log( ta.isSafeNum(-Number.MAX_SAFE_INTEGER) );  // => true 
console.log( ta.isSafeNum(0) ); // => true 

// unsafe integers
console.log( ta.isSafeNum(Number.MAX_SAFE_INTEGER + 1) ); // => false 
console.log( ta.isSafeNum(-Number.MAX_SAFE_INTEGER -1) ); // => false 
console.log( ta.isSafeNum(Number.MAX_VALUE) );            // => false  -  Number.MAX_VALUE is not a safe integer !

// Infinity is never safe
console.log( ta.isSafeNum(1/0) );                       // => false 
console.log( ta.isSafeNum(-1/0) );                      // => false 

// valid numeric strings are safe by default (acceptStringNums=true) 
console.log( ta.isSafeNum('1') );                       // => true 
console.log( ta.isSafeNum('1.1') );                     // => true 
console.log( ta.isSafeNum('-1') );                      // => true 
console.log( ta.isSafeNum('0xFF') );                    // => true 

// invalid numeric strings are never safe 
console.log( ta.isSafeNum('34.345abchs', true) );       // => false 
console.log( ta.isSafeNum('34.345abchs', false) );      // => false 

// don't allow valid numeric strings to return true  - via acceptStringNums = false
console.log( ta.isSafeNum('123', false) );              // => false 

// BigInt and BigInt literals are unsafe for use as a number 
console.log( ta.isSafeNum(1n) );                        // => false 
console.log( ta.isSafeNum(BigInt(1)) );                 // => false 

// unsafe floats 
console.log( ta.isSafeNum("999999999999999999999999.99", true) );   // => false 
console.log( ta.isSafeNum("999999999999999999999999.99", false) );  // => false 

// The Symbol type is not for safe 
console.log( ta.isSafeNum(Symbol("test")) );        // => false 

// Objects, Arrays and many built-in Javascript types (E.g. Map, Sets etc) aren't safe to use in calculations 
console.log( ta.isSafeNum(NaN) );                   // => false 
console.log( ta.isSafeNum( {} ) );                  // => false 
console.log( ta.isSafeNum( new Date()) );           // => false 
console.log( ta.isSafeNum( []) );                   // => false 
console.log( ta.isSafeNum( [1]) );                  // => false 

// Number Objects (as opposed to number primitives) aren't safe in all cases either E.g. 'new Number(99) !== 99'  !!!  
console.log( ta.isSafeNum( new Number(99) ) )       // => false

getTypeDetails( object, showFullPrototypeChain )


Performs type introspection and returns detailed type information about the object passed in. This function returns useful information about all types including ES6 / EES2020 and customs types ( E.g. Your classes )

For the returned 'type' field, the same special cases involving the use of toString( ) override and [Symbol.toStringTag] apply as per the getTypeOf function above and the same rationale applies. The goal is for the 'type' field to reveal the intrinsic underlying 'type' of an object. For built-in types and custom objects, using [Symbol.toStringTag] doesn't alter that by design here. However, we consider actual Object types an exception and return the [Symbol.toStringTag] value. This is because JavaScript's type system returns 'object' for all Objects, making it impossible to distinguish one type of custom Object from another without using [Symbol.toStringTag].

Parameters
object - The object to get type information about

showFullPrototypeChain - Optional. if true (the default value), the full javascript inheritance prototype chain will be included in the returned object. If false, the last 'Object' will be removed from the chain (which is always 'Object') and also only chains longer than 1 will be included as in this case the chain will be just have a single value the same as the Type field which is not very useful.

Returns - an object containing the following fields:

| field | default value | description | | :--- | :---: | :--- | | Type | "null" | - A string representation of the exact input type. This is set for all types not just primitives. The following types will be in lower case as per the built-in javascript typeof operator: 'string', 'number', 'boolean', 'undefined', 'symbol', 'function', 'object', 'bigint'. A Null object will be detected as 'null'. All other built-in types will be recognised and returned in CamelCase Format as per the Javascirpt standard: E.g. 'Array', 'Date', 'Error', 'RegExp', 'URL' etc. if a type can't be determined, then 'unknown' will be returned. | | ReferenceVariable | "" | A string representation of the reference variable, if any, that points to the input object. E.g. if you have 'let myfunc = function() {};' then 'Type' will be 'function' and the 'ReferenceVariable will be 'myfunc' in the returned object from getTypeDetails(myfunc). | | hasCustomConstructor| false | true if the input object has a it's own custom constructor, false otherwise. | | prototypeChainString | "" |A string representation of the Javascript inheritance prototype chain of the input object. Objects in the chain are separated by ' -> '. E.g. 'Child -> Parent -> Object'. | | prototypeChain | null | An array containing the javascript inheritance prototype chain of the input object passed. |

Examples

type details of some built-in javascript types

const ta = require('typeanalyser');

const stringDetails = ta.getTypeDetails('test');
console.log( stringDetails.Type );                              // => 'string'
console.log( stringDetails.hasCustomConstructor );              // => false
console.log( stringDetails.ReferenceVariable );                 // => ''
console.log( stringDetails.prototypeChainString );              // => 'String -> Object'
console.log( stringDetails.prototypeChain );                    // => ['String', 'Object']

const arrayDetails = ta.getTypeDetails( [] );
console.log( arrayDetails.Type );                               // => 'Array'
console.log( arrayDetails.hasCustomConstructor );               // => true
console.log( arrayDetails.ReferenceVariable );                  // => ''
console.log( arrayDetails.prototypeChainString );               // => 'Array -> Object'
console.log( arrayDetails.prototypeChain );                     // => ['Array', 'Object']

const nullDetails = ta.getTypeDetails(null);
console.log( nullDetails.Type );                                // => 'null'
console.log( nullDetails.hasCustomConstructor );                // => false
console.log( nullDetails.ReferenceVariable );                   // => ''
console.log( nullDetails.prototypeChainString );                // => ""
console.log( nullDetails.prototypeChain );                      // => null

const symbolDetails = ta.getTypeDetails( Symbol("test") );
console.log( symbolDetails.Type );                              // => 'symbol'
console.log( symbolDetails.hasCustomConstructor );              // => false
console.log( symbolDetails.ReferenceVariable );                 // => ''
console.log( symbolDetails.prototypeChainString );              // => 'Symbol -> Object'
console.log( symbolDetails.prototypeChain );                    // => ['Symbol', 'Object']

// check type details of Regular Expression
const regExDetails = ta.getTypeDetails(/hello/);
console.log( regExDetails.Type );                               // => 'RegExp'
console.log( regExDetails.hasCustomConstructor );               // => true
console.log( regExDetails.ReferenceVariable );                  // => ''
console.log( regExDetails.prototypeChainString );               // => 'RegExp -> Object'
console.log( regExDetails.prototypeChain );                     // => ['RegExp', 'Object']

// check type details of Date 
const dateDetails = ta.getTypeDetails(new Date());
console.log( dateDetails.Type );                                 // => 'Date'
console.log( dateDetails.hasCustomConstructor );                 // => true
console.log( dateDetails.ReferenceVariable );                    // => ''
console.log( dateDetails.prototypeChainString );                 // => 'Date -> Object'
console.log( dateDetails.prototypeChain );                       // => ['Date', 'Object']

let thePromise = new Promise(() => {});
const promiseDetails = ta.getTypeDetails( thePromise );
console.log( promiseDetails.Type );                              // => 'Promise'
console.log( promiseDetails.hasCustomConstructor );              // => true
console.log( promiseDetails.ReferenceVariable );                 // => ''
console.log( promiseDetails.prototypeChainString );              // => 'Promise -> Object'
console.log( promiseDetails.prototypeChain );                    // => ['Promise', 'Object']

type details of extended custom class

class Parent {};
class Child extends Parent {};
const childDetails = ta.getTypeDetails(new Child());

console.log( childDetails.Type );                               // => 'Child'
console.log( childDetails.hasCustomConstructor );               // => true
console.log( childDetails.ReferenceVariable );                  // => ''
console.log( childDetails.prototypeChainString );               // => 'Child -> Parent -> Object'
console.log( childDetails.prototypeChain );                     // => ['Child', 'Parent', 'Object']

// with shortened output prototype chain 
const childDetailsShort = ta.getTypeDetails(new Child(), false);
console.log( childDetailsShort.Type );                          // => 'Child'
console.log( childDetailsShort.hasCustomConstructor );          // => true
console.log( childDetailsShort.ReferenceVariable );             // => ''
console.log( childDetailsShort.prototypeChainString );          // => 'Child -> Parent'
console.log( childDetailsShort.prototypeChain );                // => ['Child', 'Parent']

type details of some function types

const myArrowFunction = () => {};
const arrowFunctionDetails = ta.getTypeDetails(myArrowFunction); 
console.log( arrowFunctionDetails.Type );                       // => 'ArrowFunction'
console.log( arrowFunctionDetails.hasCustomConstructor );       // => false
console.log( arrowFunctionDetails.ReferenceVariable );          // => 'myArrowFunction'
console.log( arrowFunctionDetails.prototypeChainString );       // => 'Function -> Object'
console.log( arrowFunctionDetails.prototypeChain );             // => ['Function', 'Object']

// can get details even if reference to function reference used 
let myFunc = function() {};
let otherFuncRef = myFunc;
const functionDetails = ta.getTypeDetails( otherFuncRef );
console.log( functionDetails.Type );                            // => 'function'
console.log( functionDetails.hasCustomConstructor );            // => false
console.log( functionDetails.ReferenceVariable );               // => 'myFunc'
console.log( functionDetails.prototypeChainString );            // => 'Function -> Object'
console.log( functionDetails.prototypeChain );                  // => ['Function', 'Object']

const anonFunctionDetails = ta.getTypeDetails( function() {} );
console.log( anonFunctionDetails.Type );                         // => 'function'
console.log( anonFunctionDetails.hasCustomConstructor );         // => false
console.log( anonFunctionDetails.ReferenceVariable );            // => ''
console.log( anonFunctionDetails.prototypeChainString );         // => 'Function -> Object'
console.log( anonFunctionDetails.prototypeChain );               // => ['Function', 'Object']

// Generator Function
function* genFunc() {};
const generatorDetails = ta.getTypeDetails(genFunc);
console.log( generatorDetails.Type );                            // => 'GeneratorFunction'
console.log( generatorDetails.hasCustomConstructor );            // => false
console.log( generatorDetails.ReferenceVariable );               // => 'genFunc'
console.log( generatorDetails.prototypeChainString );            // => 'GeneratorFunction -> Function -> Object'
console.log( generatorDetails.prototypeChain );                  // => ['GeneratorFunction', 'Function', 'Object']

isJSONSerializable(obj, acceptFormatLoss, visitedObjects)


Checks if an object is JSON serializable. This is a recursive function that will check all properties of an object.

Only objects which are strictly serializable will return true. Objects with functions, circular references or invalid nested types will return false.

Parameters
object - The object to check to determine if it has a circular reference

acceptFormatLoss - Optional. if false (the default), only return true for types that can be serializaed without problems.

If this parmeter is true then this function also returns true for types where no data is lost but the format is changed and can be easily converted back when de-serializing. E.g. 'Date', 'URL', 'URLsearchparams' are converted to strings when serializing to JSON, so New Date( stringValue ) or new URL( stringValue ) etc can be used to convert back.

Typed arrays are converted to regular arrays when serializing to JSON so iterating over the results of the parsed JSON element, adding to an array and then new TypedArray( array ) can be used to convert back.

visitedObjects - Used internally only to detect circular references. Do not use this parameter.

Returns
true if the object is JSON serializable WITHOUT a loss of data, false otherwise. Note that if 'acceptFormatLoss' is set then this function returns true if during JSON serialization there's no actual data loss but the format may be changed.

Examples

const ta = require('typeanalyser');

console.log( ta.isJSONSerializable({ a: 1, b: 2 }));            // => true
console.log( ta.isJSONSerializable(new Map()));                 // => false

// objects with non enumerable properties can still be serialized
let obj = { a: 1 };
Object.defineProperty( obj, 'b', { value: 2, 
                                   enumerable: false });
console.log( ta.isJSONSerializable(obj) );                      // =>  true

// Arrays with non integer properties can't be serialized without loss of information
let arr =[1, 2, 3, 4];
arr.foo = "bar"; 
console.log( ta.isJSONSerializable(arr) );                      // =>  false);

functions or objects with functions can't be JSON serialized

console.log( ta.isJSONSerializable( () => {} );                 // => false
console.log( ta.isJSONSerializable( () => {}, true );           // => false  - still false because data is lost

// nested function 
const objFn = {
    level1: {
        level2: { func: function() {}, },
    },
};
console.log( ta.isJSONSerializable( objFn ));                   // => false

Some objects can be JSON serialized but with loss of format

let today = new Date();
console.log( ta.isJSONSerializable( today, true ));             // => true - no data lost but format is changed into string
                                                            
let obj = { key: new URL('http://example.com') };
console.log( ta.isJSONSerializable( obj, true )));              // => true - value converted into a string 

let obj = { key: new Float64Array([1.1, 2.2, 3.3, 4.4]) };
console.log( ta.isJSONSerializable(obj) );                      // =>  false
console.log( ta.isJSONSerializable(obj,true) );                 // =>  true  

hasCircularRef( object, visitedObjects )


Checks if an object has a circular reference. This is a recursive function that will check all properties of an object. It works for ALL types of objects including custom and ES6 classes and is particularily useful for debugging.

However, note:
1 . It checks object properties (i.e., their state) and does not check for circular references in methods or object prototypes
2 . It won't catch circular references in dynmically created properties (i.e. created when methods are called)
3 . If a custom or ES6 class overrides the default behavior of for...in or Object.keys, there may be problems

Parameters
object - The object to check to determine if it has a circular reference

visitedObjects - Used internally to detect circular references. Do not pass this parameter

returns
true if the object has a circular reference, false otherwise.

Examples

let obj1 = {};
obj1.self = obj1;
console.log( ta.hasCircularRef(obj1));         // => true

let obj2 = { a: 1, b: 2, c: { name: 'test' } };
console.log( ta.hasCircularRef(obj2));         // => false

Installation

Node.js

Installing TypeAnalyser in a Node.js project is easy with npm. Simply use the following command:

  npm install typeanalyser

Then, in your JavaScript file, you can require the module as follows:

  const ta = require('typeanalyser');

Browsers

For browser environments, you can import TypeAnalyser via either CommonJS (CJS), ECMAScript Modules (ESM) or a simple IIFE style global module. You can use the minified or non-minified versions when debugging you app.

CommonJS (CJS)

If you're using a bundler like Browserify or Webpack, you can use the same require syntax as in Node.js:

  const ta = require('typeanalyser');

ECMAScript Modules (ESM)

In modern browsers that support ECMAScript Modules, you can import TypeAnalyser directly in your HTML file but note the use of hypen (-) in the javscript filename'. E.g;

  <script type="module">
      import * as tm from './node_modules/typeanalyser/dist/type-analyser.esm.min.js';
      // OR if you just need the essentials 
      import {getTypeOf, getNumTypeOf, isSafeNum} from './node_modules/typeanalyser/dist/type-analyser.esm.min.js';
  </script>

Note the hypen (-) in 'type-analyser'

Unbundled (IIFE) Global straight into your script

If you are not using a bundler and your app is going to run on ES5 browsers without ESM module support you can use the IIFE version of TypeAnalyser which exposes a global typeAnalyser object which will work on all browsers as is. Note the hypen (-) in the javascript file name'

  <script src="./node_modules/typeanalyser/dist/type-analyser.iife.min.js"></script>
  <script>
      var ta = typeAnalyser;  // the IIFE global is called 'typeAnalyser'

      // your code 
      if ( ta.getTypeOf(obj) === 'null' ) {
          yourObjectResetcode( obj);      
      } else {
          obj = yourObjectCreationCode( );
      }
  </script>  

CDN

You can also load TypeAnalyser directly from a CDN like jsDelivr or unpkg if you're not using npm or just want to quickly test something out. Note the hypen (-) in the javascript filename

<!-- Using ECMAScript Modules (ESM) -->
<script type="module">
  import * as tm from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/type-analyser.esm.min.js';
</script>

<!-- Or using IIFE -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/type-analyser.iife.min.js"></script>
<script>
  // Use a global object
  var ta = typeAnalyser;
  console.log(ta.getTypeOf([])); // outputs: 'Array'
</script>

Compatibility

ES6 Targets

For full compatibility with ES6 types, use the following Node or browser versions or newer:

| Node | Chrome | FireFox | Safari | Edge | Opera | IOS | Samsung Browser | Chrome Android | WebView Android | | :---:| :---: | :---: | :---: | :---: | :--: |:---:|:---: | :--: | :--: | | 7.6 | 55 | 53 | 10.2 | 15 | 43 | 11.2| 6.0 | 55 | 55 |

ES6 Targets via ES5 with Polyfills

if you are Targeting ES5 environments via ES6 Polyfills (E.g. via Babel) then TypeAnalyser will work correctly with the following Node / Browser versions or newer:

| Node | Chrome | FireFox | Safari | Edge | Opera | IOS | Samsung Browser | Chrome Android | WebView Android | | :---: | :---: | :---: | :---: |:---: |:---: |:--- | :---: | :---: |:---: | | 0.12 | 34 | 32 | 9 | 12 | 25 | 9 | 2.0 | 34 | 37 |

This requires the ES6 Polyfills to simulate the actual ES6 type. However, note that in some cases the polyfill duplicates the run-time behaviour of the type but can't simulate the actual ES6 type. E.g. This applies to Arrow and Async Functions. In practice If you don't need to detect/inspect these specific types at runtime in your code everything should work fine. E.g. you'll be able to correctly get the type of typed Arrays, or Symbol or Map objcts etc.

Setting Up the Development Environment

The source code including tests are available from the GitHub Repo (https://github.com/TheMetaGuy/Type-Analyser.git). TypeAnalyser has no run-time dependencies. Some packages (e.g., Jest, Karma, Roll-up, etc.) are needed for development, but these are installed locally only inside the project folders. Assuming you have installed Node.js v7.6 or higher, you should just need to:

> cd YourFolderName
> REM note the full stop '.' at the end so git clones into the current folder
> git clone https://github.com/TheMetaGuy/Type-Analyser.git .
> npm install 
> npm run build

Tests are in a separate 'tests' folder with its own package.json and test-scripts.

Note that there is a separate npm install step for the tests sub-folder. You may see some reported package vulnerabilities ( due to the jest-coverage-badges package of June 2023) but as these packages are just being used for tests they should not be an issue.

Batch files are used to copy files from the dist build folder and kick off the appropiate npm script as required;

> cd tests
> REM Don't miss this separate npm install step
> npm install   

To run the Jest-based Node and Browser emulation tests as defined in (\test-scripts*.tests.js), while still in the tests sub-directory, run:

> runNodeTests.bat

To use Karma to run the above Jest-based tests in the actual Chrome and Firefox browsers (in headless mode) – note that it uses whatever versions of those browsers are currently installed:

> runBrowserTests.bat

To run the special hand-crafted Iframe, Worker thread, and DOM elements tests, which automatically open up and run in the current default system browser:

> runSpecialBrowserTests.bat