SkeLLLa/node-object-hash

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# node-object-hash

<div style="margin: 24px 0 16px;">
<img src="https://gitlab.com/m03geek/node-object-hash/raw/master/logo.svg" align="left" width="256" height="auto" alt="logo" />

Tiny and fast node.js object hash library with properties/arrays sorting to provide constant hashes.
It also provides a method that returns sorted object strings that can be used for object comparison without hashes.
One of the fastest among other analogues (see [benchmarks](#benchmarks)).

Hashes are built on top of node's crypto module. If you want to use it in browser it's recommented to use `objectSorter` only. It will provide you with unique string representation of your object. Afterwards you may use some hash library to reduce string size. Also you may use something like [browserify-crypto](https://github.com/crypto-browserify/crypto-browserify) or some kind of crypto functions polyfills.

[![Node](https://img.shields.io/node/v/node-object-hash.svg)](https://nodejs.org/download/release/latest)
[![NPM Version](https://img.shields.io/npm/v/node-object-hash.svg)](https://www.npmjs.com/package/node-object-hash)
[![Downloads Count](https://img.shields.io/npm/dm/node-object-hash.svg)](https://www.npmjs.com/package/node-object-hash)
[![Vunerabilities Count](https://snyk.io/test/npm/node-object-hash/badge.svg)](https://www.npmjs.com/package/node-object-hash)
[![Npms.io Score](https://badges.npms.io/node-object-hash.svg)](https://npms.io/search?q=node-object-hash)
[![Build Status](https://github.com/SkeLLLa/node-object-hash/workflows/build/badge.svg)](https://github.com/SkeLLLa/node-object-hash/commits/master)
[![License](https://img.shields.io/npm/l/node-object-hash.svg)](https://gitlab.com/m03geek/node-object-hash/blob/master/LICENSE)
[![Codecov Coverage](https://codecov.io/gh/SkeLLLa/node-object-hash/branch/master/graph/badge.svg?token=wLjMou8TT7)](https://codecov.io/gh/SkeLLLa/node-object-hash)

</div>

<br/>

## ToC

- [node-object-hash](#node-object-hash)
  - [ToC](#toc)
  - [What's new in v3.0.0](#whats-new-in-v300)
  - [What's new in v2.0.0](#whats-new-in-v200)
    - [Breaking changes](#breaking-changes)
    - [New features](#new-features)
  - [Installation](#installation)
  - [Features](#features)
    - [Type map](#type-map)
    - [Coercion map](#coercion-map)
  - [Changes](#changes)
  - [Docs](#docs)
    - [API overview](#api-overview)
      - [Constructor](#constructor)
    - [API methods](#api-methods)
      - [`hash(object[, options])`](#hashobject-options)
      - [`sort(object)`](#sortobject)
    - [Hashing custom objects](#hashing-custom-objects)
  - [Requirements](#requirements)
    - [version \>=1.0.0](#version-100)
    - [version \>=0.1.0 \&\& \<1.0.0](#version-010--100)
  - [Examples](#examples)
  - [Benchmarks](#benchmarks)
    - [Usage](#usage)
    - [Results](#results)
      - [Custom benchmark (code)](#custom-benchmark-code)
      - [Benchmark suite module (code)](#benchmark-suite-module-code)
    - [Links](#links)
  - [License](#license)

## What's new in v3.0.0

**Disclaimer**: No new features or changes that may break hashes from previous versions. There's no need to update unless you're starting project from scratch.

- Refactor and migration to typescript 5.
- Drop old node support.
- Removed typescript namespaces.
- Updated exported functions and object structure.
- Removed faker and old benchmarks.
- New CI and release automation.

## What's new in v2.0.0

### Breaking changes

- Library rewritten in typescript that could cause some side-effects, but it should not.
- With `coerce=false` `Set`s will no longer generate the same hashes as `Array`s. In order to restore previous behavior set `coerce.set=true`.
- With `coerce=false` `Symbol`s will generate hash based on symbol `.toString` value. That's useful for `Symbol.for('smth')`. If `coerce.symbol=true` all `Symbols`s will have equal hashes.
  TLDR; If you use library with `Set`s or `Symbol`s with `coerce=false` in order to keep hashes the same as in `v1.X.X` you should use following constructor:

```
const hasher = require('node-object-hash')({coerce: {set: true, symbol: true}})
```

- Object sorter sources moved to `dist` directory. If you required it directly via `require('node-object-hash/objectSorter')` you should change it to require('node-object-hash/dist/objectSorter').
- Removed old `v0` version from code.
- Changed license to MIT.

### New features

- New granular options. Now you can specify what types need to be sorted or coerced.
- Add new `trim` option. It can be used to remove unncecessary spaces in `string`s or `function` bodies.
- Library rewritten to typescript, so it may have better ts compatibility.

## Installation

`npm i node-object-hash -S`

## Features

- Supports object property sorting for constant hashes for objects with same properties, but different order.
- Supports ES6 Maps and Sets.
- Supports type coercion (see table below).
- Supports all hashes and encodings of crypto library.
- Supports large objects and arrays.
- Has granular options that allows to control what should be sorted or coerced.
- Very fast comparing to other libs (see [Benchmarks](#benchmarks) section).

### Type map

This map displays what types will have identical string representation (e.g. new Set([1, 2, 3]) and [1, 2, 3] will have
equal string representations and hashes.

| Initial type              | Mapped type  |
| ------------------------- | ------------ |
| Array ([])                | array        |
| ArrayObject (new Array()) |              |
| Int8Array                 |              |
| Uint8Array                |              |
| Uint8ClampedArray         |              |
| Int16Array                |              |
| Uint16Array               |              |
| Int32Array                |              |
| Uint32Array               |              |
| Float32Array              |              |
| Float64Array              |              |
| Buffer                    |              |
| Set                       |              |
|                           |              |
| Map                       | array[array] |
|                           |              |
| string ('')               | string       |
| String (new String())     |              |
|                           |              |
| boolean (true)            | boolean      |
| Boolean (new Boolean())   |              |
|                           |              |
| number (true)             | number       |
| Number (new Number())     |              |
|                           |              |
| Date                      | date         |
|                           |              |
| Symbol                    | symbol       |
|                           |              |
| undefined                 | undefined    |
|                           |              |
| null                      | null         |
|                           |              |
| function                  | function     |
|                           |              |
| Object ({})               | object       |
| Object (new Object())     |              |
|                           |              |
| other                     | unknown      |

### Coercion map

| Initial "type" | Coerced type   | Example      |
| -------------- | -------------- | ------------ |
| boolean        | string         | true -> 1    |
| number         | string         | '1' -> 1     |
| string         | string         | 'a' -> a     |
| null           | string (empty) | null ->      |
| undefined      | string (empty) | undefined -> |

## Changes

See [changelog](docs/CHANGELOG.md)
For v2 changes see [changelog-v2](docs/CHANGELOG-v2.md)

## Docs

Full API docs could be found in [docs](./docs/api/README.md).

### API overview

#### Constructor

```js
require('node-object-hash').hasher([options]);
```

Returns preconfigured object with API

Parameters:

- `options`:`object` - object with hasher config options
- `options.coerce`:`boolean|object` - if true performs type coercion (default: `true`);
  e.g. `hash(true) == hash('1') == hash(1)`, `hash(false) == hash('0') == hash(0)`
- `options.sort`:`boolean|object` - if true performs sorting on objects, arrays, etc. (default: `true`); in order to
  perform sorting on `TypedArray` (`Buffer`, `Int8Array`, etc.), specify it explicitly: `typedArray: true`
- `options.trim`:`boolean|object` - if true performs trim of spaces and replaces space-like characters with single space (default: `false`);
- `options.alg`:`string` - sets default hash algorithm (default: `'sha256'`); can be overridden in `hash` method;
- `options.enc`:`string` - sets default hash encoding (default: `'hex'`); can be overridden in `hash` method;

### API methods

#### `hash(object[, options])`

Returns hash string.

- `object`:`*` object for calculating hash;
- `options`:`object` object with options;
- `options.alg`:`string` - hash algorithm (default: `'sha256'`);
- `options.enc`:`string` - hash encoding (default: `'hex'`);

#### `sort(object)`

Returns sorted string generated from object (can be used for object comparison)

- `object`:`*` - object for sorting;

### Hashing custom objects

In order to serialize and hash your custom objects you may provide `.toHashableString()` method for your object. It should return `string` that will be hashed. You may use `objectSorter` and pass notable fields to it in your `.toHashableString` method.

For typescript users you may add to your classes `implements Hashable`.

## Requirements

### version \>=1.0.0

- `>=nodejs-0.10.0`

### version \>=0.1.0 && <1.0.0

- `>=nodejs-6.0.0`
- `>=nodejs-4.0.0` (requires to run node with `--harmony` flag)

## Examples

```js
var { hasher } = require('node-object-hash');

var hashSortCoerce = hasher({ sort: true, coerce: true });
// or
// var hashSortCoerce = hasher();
// or
// var hashSort = hasher({sort:true, coerce:false});
// or
// var hashCoerce = hasher({sort:false, coerce:true});

var objects = {
  a: {
    a: [{ c: 2, a: 1, b: { a: 3, c: 2, b: 0 } }],
    b: [1, 'a', {}, null],
  },
  b: {
    b: ['a', 1, {}, undefined],
    a: [{ c: '2', b: { b: false, c: 2, a: '3' }, a: true }],
  },
  c: ['4', true, 0, 2, 3],
};

hashSortCoerce.hash(objects.a) === hashSortCoerce.hash(objects.b);
// returns true

hashSortCoerce.sort(object.c);
// returns '[0,1,2,3,4]'
```

For more examples you can see [tests](./test) or try it out online at [runkit](https://runkit.com/skellla/node-object-hash-example)

## Benchmarks

Bench data - array of 100000 complex objects

### Usage

- `npm run bench` to run custom benchmark
- `npm run benchmark` to run benchmark suite
- `npm run benchmark:hash` to run hash benchmark suite

### Results

| Hashing algorithm  | Result hash bytes length | Performance (ops/sec) |
| ------------------ | ------------------------ | --------------------- |
| `sha256` (default) | 64                       | 1,599 +- 5.77%        |
| `sha1`             | 40                       | 1,983 +- 1.50%        |
| `sha224`           | 56                       | 1,701 +- 2.81%        |
| `sha384`           | 96                       | 1,800 +- 0.81%        |
| `sha512`           | 128                      | 1,847 +- 1.75%        |
| `md4`              | 32                       | 1,971 +- 0.98%        |
| `md5`              | 32                       | 1,691 +- 3.18%        |
| `whirlpool`        | 128                      | 1,487 +- 2.33%        |
|                    |                          |                       |

#### Custom benchmark ([code](bench/index.js))

| Library                           | Time (ms)  | Memory (Mb)        |
| --------------------------------- | ---------- | ------------------ |
| node-object-hash-0.2.1            | 5813.575   | 34                 |
| node-object-hash-1.0.X            | 2805.581   | 27                 |
| node-object-hash-1.1.X (node v7)  | 2555.583   | 27                 |
| node-object-hash-1.2.X (node v7)  | 2390.752   | 28                 |
| node-object-hash-2.X.X (node v12) | 1990.622   | 24                 |
| object-hash-1.1.5 (node v7)       | 28115.553  | 39                 |
| object-hash-1.1.4                 | 534528.254 | 41                 |
| object-hash-1.1.3                 | ERROR      | Out of heap memory |
| hash-object-0.1.7                 | 9219.826   | 42                 |

#### Benchmark suite module ([code](bench/bench.js))

| Library (node v12)     | Perf (ops/s) |
| ---------------------- | ------------ |
| node-object-hash-2.0.0 | 2087 ±0.59%  |
| object-hash-1.3.1      | 239 ±0.39%   |
| hash-object-0.1.7      | 711 ±0.18%   |

### Links

- [object-hash](https://www.npmjs.com/package/object-hash) - Slow, useful for browsers because it not uses node's crypto library
- [hash-object](https://www.npmjs.com/package/hash-object) - no ES6 types support

## License

MIT