stable-hash-x
A tiny and fast (460b unpkg) lib for "stably hashing" a JavaScript value. Originally created for SWR by Shu Ding at stable-hash
, we forked it because the original one is a bit out of maintenance for a long time.
It's similar to JSON.stringify(value)
, but:
- Supports any JavaScript value (
BigInt
,NaN
,Symbol
,function
,class
, ...) - Sorts object keys (stable)
- Supports circular objects
TOC
Use
yarn add stable-hash-x
import { hash } from 'stable-hash-x'
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 Symbol
s cannot be serialized, stable-hash-x
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-x
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]))
Benchmark
clk: ~2.91 GHz
cpu: Apple M1 Max
runtime: node 22.16.0 (arm64-darwin)
benchmark avg (min … max) p75 / p99 (min … top 1%)
------------------------------------------- -------------------------------
stable-hash-x 7.87 µs/iter 7.38 µs █
(6.67 µs … 749.13 µs) 11.42 µs ▇█▃
(104.00 b … 859.30 kb) 10.89 kb ▁███▅▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁
4.41 ipc ( 1.81% stalls) 98.08% L1 data cache
28.04k cycles 123.52k instructions 29.75% retired LD/ST ( 36.75k)
hash-object 15.07 µs/iter 14.95 µs █ █
(14.77 µs … 16.93 µs) 15.00 µs ▅ ▅ ▅▅█ ▅█▅ ▅
(659.78 b … 3.26 kb) 1.95 kb █▁▁█▁▁▁▁▁▁███▁▁███▁▁█
4.97 ipc ( 1.22% stalls) 99.33% L1 data cache
46.36k cycles 230.44k instructions 35.12% retired LD/ST ( 80.94k)
json-stringify-deterministic 8.37 µs/iter 8.41 µs █
(8.29 µs … 8.50 µs) 8.44 µs █ █
( 1.65 kb … 1.65 kb) 1.65 kb █▁████▁██▁█▁▁▁█▁█▁███
5.17 ipc ( 1.28% stalls) 99.40% L1 data cache
25.99k cycles 134.30k instructions 35.51% retired LD/ST ( 47.69k)
summary
stable-hash-x
1.06x faster than json-stringify-deterministic
1.91x faster than hash-object
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-x
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 'node:crypto'
import { hash } from 'stable-hash-x'
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.
Sponsors and Backers
Sponsors
1stG | RxTS | UnRS | UnTS |
---|---|---|---|
Backers
1stG | RxTS | UnRS | UnTS |
---|---|---|---|
Changelog
Detailed changes for each release are documented in CHANGELOG.md.
License
Originally created by Shu Ding.