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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@locustjs/compare

v2.2.0

Published

This library provides abstractions for comparison and comparers

Readme

@locustjs/compare

This library provides classes for equality comparison and comparers. These comparers are useful mainly when working with complex data types like arrays and objects.

One good usecase is when we want to find an element in an aray of objects and prefer to perform a shape comparison. We cannot use the ordinary Array find method, because it performs a reference comparison.

Current Version

2.1.0

Equality Comparers

In order for a class to be an equality comparer it should derive from a base abstract class named EqualityComparer and implement an equals(x, y) method. This method is used to check whether the two arguments x and y are equal or not.

class EqualityComparer {
    equals(x, y) {
        throwNotImplementedException(`equals(x, y)`);
    }
}

List of equality comparers:

  • NullOrEmptyEqualityComparer
  • PrimitiveEqualityComparer
  • StringEqualityComparer
  • ObjectEqualityComparer
  • ArrayEqualityComparer
  • IterableEqualityComparer
  • FunctionEqualityComparer

There is a BaseEqualityComparer class derived from EqualityComparer with a common functionality for performing equality comparison. All of the above equality comparers (except NullOrEmptyEqualityComparer) derive from BaseEqualityComparer.

NullOrEmptyEqualityComparer

This class is used to check equality of null, undefined, NaN or empty (zero-length) strings.

By default it uses loose comparison which assumes null, undefined, NaN and empty string ('') are equal.

Example:

const ec = new NullOrEmptyEqualityComparer();   // loose equality comparison

console.log(ec.equals(null, undefined)) // true
console.log(ec.equals(undefined, '')) // true
console.log(ec.equals(undefined, NaN)) // true
console.log(ec.equals(null, '')) // true
console.log(ec.equals(NaN, '')) // true
console.log(ec.equals(null, NaN)) // true

In order to perform a strict or tight comparison we can pass a true argument to NullOrEmptyEqualityComparer constructor.

const ec = new NullOrEmptyEqualityComparer(true);

console.log(ec.equals(null, undefined)) // false
console.log(ec.equals(null, NaN)) // false
console.log(ec.equals(undefined, '')) // false
console.log(ec.equals(null, '')) // false
console.log(ec.equals(null, null)) // true
console.log(ec.equals(undefined, undefined)) // true

NullOrEmptyEqualityComparer has two helper static props that return default NullOrEmptyEqualityComparer instances with proper setting, making it easier to use NullOrEmptyEqualityComparer withought manually instantiating from this class:

  • Loose: returns a new NullOrEmptyEqualityComparer(false) instance.
  • Tight: returns a new NullOrEmptyEqualityComparer(true) instance.
console.log(NullOrEmptyEqualityComparer.Loose.equals(null, undefined)) // true
console.log(NullOrEmptyEqualityComparer.Tight.equals(null, undefined)) // false

Also, EqualityComparer class provides a static Null property which returns an object with Loose and Tight that refer to NullOrEmptyEqualityComparer.Loose and NullOrEmptyEqualityComparer.Tight static props respectively.

console.log(EqualityComparer.Null.Loose.equals(null, undefined)) // true
console.log(EqualityComparer.Null.Tight.equals(null, undefined)) // false

StringEqualityComparer

This class is used to check equality of strings.

Example:

const ec = new StringEqualityComparer();

console.log(ec.equals("hello", "Hello")) // false

By default, it performs ordinal comparison. By passing a StringEqualityComparerType enum or string value to its constructor, it can perform ignore-case comparison.

const ec = new StringEqualityComparer(StringEqualityComparerType.ignoreCase);
// or ec = new StringEqualityComparer('ignoreCase');

console.log(ec.equals("hello", "Hello")) // true

StringEqualityComparer has 4 helper static props that return default StringEqualityComparer instances with proper setting, making it easier to use StringEqualityComparer withought manually instantiating from this class:

  • LooseOrdinal: returns a new StringEqualityComparer({ string: "ordinal", primitive: "loose" }); instance.
  • LooseIgnoreCase: returns a new StringEqualityComparer({ string: "ignoreCase", primitive: "loose" }); instance.
  • TightOrdinal: returns a new StringEqualityComparer({ string: "ordinal", primitive: "tight" }); instance.
  • TightOrdinal: returns a new StringEqualityComparer({ string: "ordinal", primitive: "tight" }); instance.
  • Ordinal: returns the same instance as LooseOrdinal.
  • IgnoreCase: returns the same instance as LooseIgnoreCase.
const s1 = "hello";
const s2 = "Hello";

console.log(StringEqualityComparer.Ordinal.equals(s1, s2)) // false
console.log(StringEqualityComparer.IgnoreCase.equals(s1, s2)) // true

Also, EqualityComparer class provides a static String property which returns an object with relevant properties that refer to the similar static property in StringEqualityComparer.

const s1 = "hello";
const s2 = "Hello";

console.log(EqualityComparer.String.Ordinal.equals(s1, s2)) // false
console.log(EqualityComparer.String.IgnoreCase.equals(s1, s2)) // true

PrimitiveEqualityComparer

This class is used to check equality of primitve data types (number, boolean, string, date).

Example:

const ec = new PrimitiveEqualityComparer();

console.log(ec.equals(25, "25")) // true
console.log(ec.equals(true, "true")) // true
console.log(ec.equals(true, new Boolean(true))) // true

By default, it performs loose comparison, i.e., 25 equals "25" or true equals "true". By passing false to its constructor, we can ask a tight comparison to be made. It should be pointed out that, in tight mode, bool and Boolean are still asumed equal.

const ec = new PrimitiveEqualityComparer(false);

console.log(ec.equals(25, "25")) // false
console.log(ec.equals(true, "true")) // false
console.log(ec.equals(true, new Boolean(true))) // true

PrimitiveEqualityComparer has 2 helper static props that return default PrimitiveEqualityComparer instances with proper setting, making it easier to use PrimitiveEqualityComparer withought manually instantiating from this class:

  • Loose: returns a new PrimitiveEqualityComparer(false); instance.
  • Tight: returns a new PrimitiveEqualityComparer(true); instance.
console.log(PrimitiveEqualityComparer.Loose.equals(25, "25")) // true
console.log(PrimitiveEqualityComparer.Tight.equals(25, "25")) // false

Also, EqualityComparer class provides a static Primitive property which returns an object with relevant properties that refer to the similar static property in PrimitiveEqualityComparer.

console.log(EqualityComparer.Primitive.Loose.equals(25, "25")) // true
console.log(EqualityComparer.Primitive.Tight.equals(25, "25")) // false

ArrayEqualityComparer

This class is used to check equality of arrays.

Example:

const ec = new ArrayEqualityComparer();

console.log(ec.equals([25], ["25"])) // true
console.log(ec.equals([{ a: 25 }], [{ a: "25" }])) // true

By default, it performs loose-shape comparison, i.e., for primitive data types the comparison is loose and for complex data types (objects, arrays, iterables) the comparison is by shape. By passing a EqualityComparerType enum value, this can be changed.

const ec = new ArrayEqualityComparer(EqualityComparerType.looseRef);

console.log(ec.equals([25], ["25"])) // true
console.log(ec.equals([{ a: 25 }], [{ a: 25 }])) // false

ec = new ArrayEqualityComparer(EqualityComparerType.tightShape);

console.log(ec.equals([25], ["25"])) // false
console.log(ec.equals([{ a: 25 }], [{ a: 25 }])) // true

ec = new ArrayEqualityComparer(EqualityComparerType.tightRef);

console.log(ec.equals([25], ["25"])) // false
console.log(ec.equals([{ a: 25 }], [{ a: 25 }])) // false

ArrayEqualityComparer has 4 helper static props that return default ArrayEqualityComparer instances with proper setting, making it easier to use ArrayEqualityComparer withought manually instantiating from this class:

  • LooseShape: returns a new ArrayEqualityComparer(EqualityComparerOptions.LooseShape); instance.
  • TightShape: returns a new ArrayEqualityComparer(EqualityComparerOptions.TightShape); instance.
  • LooseRef: returns a new ArrayEqualityComparer(EqualityComparerOptions.LooseRef); instance.
  • TightRef: returns a new ArrayEqualityComparer(EqualityComparerOptions.TightRef); instance.
console.log(ArrayEqualityComparer.LooseShape.equals([25], ["25"])) // true
console.log(ArrayEqualityComparer.TightShape.equals([25], ["25"])) // false

Also, EqualityComparer class provides a static Array property which returns an object with relevant properties that refer to the similar static property in ArrayEqualityComparer.

console.log(EqualityComparer.Array.LooseShape.equals([25], ["25"])) // true
console.log(EqualityComparer.Array.TightShape.equals([25], ["25"])) // false

IterableEqualityComparer

This class is used to check equality of iterables (anything that returns an iterator and could be iterated).

Example:

const ec = new IterableEqualityComparer();

function* f1() {
	yield 25;
}
function* f2() {
	yield "25";
}

console.log(ec.equals(f1(), f2())) // true
const ec = new IterableEqualityComparer();

function* f1() {
	yield { a: 25 };
}
function* f2() {
	yield { a: "25" };
}

console.log(ec.equals(f1(), f2())) // true

By default, it performs loose-shape comparison, i.e., for primitive data types the comparison is loose and for complex data types (objects, arrays and iterables) the comparison is by shape. By passing a EqualityComparerType enum value, this can be changed.

IterableEqualityComparer has 4 helper static props that return default IterableEqualityComparer instances with proper setting, making it easier to use IterableEqualityComparer withought manually instantiating from this class:

  • LooseShape: returns a new IterableEqualityComparer(EqualityComparerOptions.LooseShape); instance.
  • TightShape: returns a new IterableEqualityComparer(EqualityComparerOptions.TightShape); instance.
  • LooseRef: returns a new IterableEqualityComparer(EqualityComparerOptions.LooseRef); instance.
  • TightRef: returns a new IterableEqualityComparer(EqualityComparerOptions.TightRef); instance.
function* f1() {
	yield 25;
}
function* f2() {
	yield "25";
}

console.log(IterableEqualityComparer.LooseShape.equals(f1(), f2())) // true
console.log(IterableEqualityComparer.TightShape.equals(f1(), f2())) // false

Also, EqualityComparer class provides a static Iterable property which returns an object with relevant properties that refer to the similar static property in IterableEqualityComparer.

function* f1() {
	yield 25;
}
function* f2() {
	yield "25";
}

console.log(EqualityComparer.Iterable.LooseShape.equals(f1(), f2())) // true
console.log(EqualityComparer.Iterable.TightShape.equals(f1(), f2())) // false

if the two arguments passed to IterableEqualityComparer are arryas, it uses two internal ObjectEqualityComparer and ArrayEqualityComparer instances to compare the arguments.

ObjectEqualityComparer

This class is used to check equality of Objects.

Example 1:

const ec = new ObjectEqualityComparer();

const a = { x; 10, y: 23.5, name: "A101" }
const b = { x; "10", y: "23.5", name: "A101" }

console.log(ec.equals(a, b)) // true

Example 1:

const ec = new ObjectEqualityComparer();

const p1 = {
    name: "Ali",
    location: {
        city: {
            id: 101,
            name: "Tehran"
        }
    },
    points: [17.5, 19.25, 14.0 ],
    addresses: [
        { id: 5274, value: "address 1", default: true },
        { id: 5705, value: "address 2", default: false }
    ]
}
const p2 = {
    name: "Ali",
    location: {
        city: {
            id: 101,
            name: "Tehran"
        }
    },
    points: [17.5, 19.25, 14.0 ],
    addresses: [
        { id: 5274, value: "address 1", default: true },
        { id: 5705, value: "address 2", default: false }
    ]
}

console.log(ec.equals(p1, p2)) // true

By default, it performs loose-shape comparison, i.e., for primitive data types the comparison is loose and for complex data types the comparison is by shape.

By passing a EqualityComparerType enum value, this can be changed.

ObjectEqualityComparer has 4 helper static props that return default ObjectEqualityComparer instances with proper setting, making it easier to use ObjectEqualityComparer withought manually instantiating from this class:

  • LooseShape: returns a new ObjectEqualityComparer(EqualityComparerOptions.LooseShape); instance.
  • TightShape: returns a new ObjectEqualityComparer(EqualityComparerOptions.TightShape); instance.
  • LooseRef: returns a new ObjectEqualityComparer(EqualityComparerOptions.LooseRef); instance.
  • TightRef: returns a new ObjectEqualityComparer(EqualityComparerOptions.TightRef); instance.
const a = { x; 10, y: 23.5, name: "A101" }
const b = { x; "10", y: "23.5", name: "A101" }

console.log(ObjectEqualityComparer.TightShape.equals(a, b)) // false

Also, EqualityComparer class provides a static Object property which returns an object with relevant properties that refer to the similar static property in ObjectEqualityComparer.

const a = { x; 10, y: 23.5, name: "A101" }
const b = { x; "10", y: "23.5", name: "A101" }

console.log(EqualityComparer.Object.TightShape.equals(a, b)) // false

If an object includes nested objects or arrays, ObjectEqualityComparer uses itself and an internal ArrayEqualityComparer and IterableEqualityComparer instances recursively to compare the arguments.

FunctionEqualityComparer

This class is used to check equality of functions.

Example:

const ec = new FunctionEqualityComparer();
const f1 = function() { }
const f2 = function() { }
const f3 = f1;

console.log(ec.equals(f1, f2)) // false
console.log(ec.equals(f3, f1)) // true

By default, FunctionEqualityComparer checks reference equality. By passing a FunctionEqualityComparerType enum value or a string value to its constructor, it can perform source equality.

const ec = new FunctionEqualityComparer(FunctionEqualityComparerType.source);
// or ec = new FunctionEqualityComparer('source');

const f1 = function() { }
const f2 = function() { }

console.log(ec.equals(f1, f2)) // true

FunctionEqualityComparer has 2 helper static props that return default FunctionEqualityComparer instances with proper setting, making it easier to use FunctionEqualityComparer withought manually instantiating from this class:

  • Ref: returns a new FunctionEqualityComparer(FunctionEqualityComparerType.ref); instance.
  • Source: returns a new FunctionEqualityComparer(FunctionEqualityComparerType.source); instance.
  • Ignore: returns a new FunctionEqualityComparer(FunctionEqualityComparerType.ignore); instance.
const f1 = function() { }
const f2 = function() { }

console.log(FunctionEqualityComparer.Ref.equals(f1, f2)) // false
console.log(FunctionEqualityComparer.Source.equals(f1, f2)) // true

Also, EqualityComparer class provides a static Function property which returns an object with relevant properties that refer to the similar static property in FunctionEqualityComparer.

const f1 = function() { }
const f2 = function() { }

console.log(EqualityComparer.Function.Ref.equals(f1, f2)) // false
console.log(EqualityComparer.Function.Source.equals(f1, f2)) // true

Enums

ScalerEqualityComparerType

{
    loose: 0,   // == comparison
    tight: 1,   // === comparison
}

ComplexEqualityComparerType

{
    shape: 0,   // compare objects' shape
    ref: 1,     // compare objects' references
}

FunctionEqualityComparerType

{
    ref: 0,     // checking functions' references (default)
    source: 1,  // checking functions' source-codes
    ignore: 2,  // ignore equality checking and assume the functions are equal
}

EqualityComparerType

{
    looseShape: 0,
    looseRef: 1,
    tightShape: 2,
    tightRef: 3,
}

StringEqualityComparerType

{
    ordinal: 0,
    ignoreCase: 1,
}

Customizing Equality Comparers

All equality comparers that derive from EqualityComparer have an options prop. It has an option with the following structure:

{
    null: string // e.g. "loose",
    primitive: string // e.g. "loose",
    string: string // e.g. "ordinal",
    array: string // e.g. "shape",
    iterable: string // e.g. "shape",
    object: string // e.g. "shape",
    function: string // e.g. "ref"
}

example: loose-shape equality comparison option:

{
    null: "loose",
    primitive: "loose",
    string: "ordinal",
    array: "shape",
    iterable: "shape",
    object: "shape",
    function: "ref",
}

It specifies how an equality comparer should behave, based on the type of arguments passed to equals() method. This is mainly important for complex equality comparers such as ArrayEqualityComparer or ObjectEqualityComparer.

By manipulating a specific prop in the options, we can customize the existing equality comparers' behavior.

For example, we may want an ObjectEqualityComparer, not to perform a loose comparison on null values.

To do this, we can pass an options object to constructor of the equality comparer.

const ec = new ObjectEqualityComparer({
    null: "tight",
    primitive: "loose",
    string: "ordinal",
    array: "shape",
    iterable: "shape",
    object: "shape",
    function: "ref",
})

By default, EqualityComparer performs a loose-shape comparison. So, we just need to customized the prop we want.

const ec = new ObjectEqualityComparer({
    null: "tight"
})

The value of each prop can also be a function. This way, it is possible to perform a custom logic when doing an equality comparison for a specific data type.

const ec = new ObjectEqualityComparer({
    null: (x, y) => isNullOrUndefined(x) && isNullOrUndefined(y) && x == y
})

Here, we customized our ObjectEqualityComparer in a way that it only assumes null and undefined to be equal, not null, undefined and empty (zero-length) strings, which is the default behavior.

We can also define a class for such equality comparer for later use.

class MyObjectEqualityComparer extends ObjectEqualityComparer {
    constructor() {
        super({
            null: (x, y) => isNullOrUndefined(x) && isNullOrUndefined(y) && x == y
        })
    }
}

Practical example

Equality comparer is mainly used in find, lookup, search and filtering.

Assuming we have an rray of objects, we can write a findElementIndex method that looks up an element, based on its shape.

function findElementIndex(arr, obj) {
    return arr.findIndex(x => ObjectEqualityComparer.LooseShape.equals(x, obj))
}
function elementExists(arr, obj) {
    return findIndex(arr, obj) >= 0
}

const data = [
    { id: 10, code: "CB101", size: 27.46 },
    { id: 11, code: "F5H01", size: 43.61 },
    { id: 16, code: "MK927", size: 19.37 },
    { id: 13, code: "WO325", size: 72.02 },
    { id:  5, code: "ZP664", size: 36.37 },
    { id: 22, code: "XH237", size: 52.19 },
    { id: 12, code: "JQ018", size: 42.84 },
    { id: 20, code: "RU845", size: 65.92 },
    { id: 14, code: "TY362", size: 18.16 },
]

const x = { id:  5, code: "ZP664", size: 36.37 }

console.log(elementExists(data, x));    // true

We can also, extend functionality of native Array.find and Array.findIndex and support argument lookup, isntead of function predicates.

const _native_array_find = Array.prototype.find;
const _native_array_findIndex = Array.prototype.findIndex;

Array.prototype.find = function (arg, equalityComparer) {
    if (isFunction(arg)) {
        return _native_array_find.call(this, arg)
    }

    const _equalityComparer = equalityComparer ?? ObjectEqualityComparer.LooseShape;

    return _native_array_find(x => _equalityComparer.equals(x, arg))
}
Array.prototype.findIndex = function (arg, equalityComparer) {
    if (isFunction(arg)) {
        return _native_array_findIndex.call(this, arg)
    }

    const _equalityComparer = equalityComparer ?? ObjectEqualityComparer.LooseShape;

    return _native_array_findIndex(x => _equalityComparer.equals(x, arg))
}

Our final code will be this way:

const x = { id:  5, code: "ZP664", size: 36.37 }

// normal .find() usage (passing a predict callback)
const y = data.find(a => a.id == x.id);

console.log(y == undefined);    // false
console.log(y == x);            // false

// usage 1: passing value, using default comparer
const y1 = data.find(x);

console.log(y1 == undefined);   // false
console.log(y1 == x);           // false
console.log(data.findIndex(x)); // 4

// usage 2: passing value, using custom comparer
const y2 = data.find(x, ObjectEqualityComparer.TightShape);

console.log(y2 == undefined);   // true

const y3 = data.find(data[4], ObjectEqualityComparer.TightShape);

console.log(y2 == undefined);   // false
console.log(y2 == x);           // true