/stable-hash

Stable JS value hash

Primary LanguageTypeScript

stable-hash

A tiny and fast (481b unpkg) lib for "stably hashing" a JavaScript value. Originally created for SWR.

It's similar to JSON.stringify(value), but:

  1. Supports any JavaScript value (BigInt, NaN, Symbol, function, class, ...)
  2. Sorts object keys (stable)
  3. Supports circular objects

Use

yarn add stable-hash
import hash from 'stable-hash'

hash(anyJavaScriptValueHere) // returns a string

Examples

Primitive Value

hash(1)
hash('foo')
hash(true)
hash(undefined)
hash(null)
hash(NaN)

BigInt:

hash(1) === hash(1n)
hash(1) !== hash(2n)

Symbol:

hash(Symbol.for('foo')) === hash(Symbol.for('foo'))
hash(Symbol.for('foo')) === hash(Symbol('foo'))
hash(Symbol('foo')) === hash(Symbol('foo'))
hash(Symbol('foo')) !== hash(Symbol('bar'))

Since Symbols cannot be serialized, stable-hash simply uses its description as the hash.

Regex

hash(/foo/) === hash(/foo/)
hash(/foo/) !== hash(/bar/)

Date

hash(new Date(1)) === hash(new Date(1))

Array

hash([1, '2', [new Date(3)]]) === hash([1, '2', [new Date(3)]])
hash([1, 2]) !== hash([2, 1])

Circular:

const foo = []
foo.push(foo)
hash(foo) === hash(foo)

Object

hash({ foo: 'bar' }) === hash({ foo: 'bar' })
hash({ foo: { bar: 1 } }) === hash({ foo: { bar: 1 } })

Stable:

hash({ a: 1, b: 2, c: 3 }) === hash({ c: 3, b: 2, a: 1 })

Circular:

const foo = {}
foo.foo = foo
hash(foo) === hash(foo)

Function, Class, Set, Map, Buffer...

stable-hash guarantees reference consistency (===) for objects that the constructor isn't Object.

const foo = () => {}
hash(foo) === hash(foo)
hash(foo) !== hash(() => {})
class Foo {}
hash(Foo) === hash(Foo)
hash(Foo) !== hash(class {})
const foo = new Set([1])
hash(foo) === hash(foo)
hash(foo) !== hash(new Set([1]))

Notes

This function does something similar to JSON.stringify, but more than it. It doesn't generate a secure checksum, which usually has a fixed length and is hard to be reversed. With stable-hash it's still possible to get the original data. Also, the output might include any charaters, not just alphabets and numbers like other hash algorithms. So:

  • Use another encoding layer on top of it if you want to display the output.
  • Use another crypto layer on top of it if you want to have a secure and fixed length hash.
import crypto from 'crypto'
import hash from 'stable-hash'

const weakHash = hash(anyJavaScriptValueHere)
const encodedHash = Buffer.from(weakHash).toString('base64')
const safeHash = crypto.createHash('MD5').update(weakHash).digest('hex')

Also, the consistency of this lib is sometimes guaranteed by the singularity of the WeakMap instance. So it might not generate the consistent results when running in different runtimes, e.g. server/client or parent/worker scenarios.

License

Created by Shu Ding. Released under the MIT License.