mirror: A fast canonicalisation utility for stable object equality
0

Configure Feed

Select the types of activity you want to include in your feed.

chore: code down to 535B

Marais Rossouw (Jun 25, 2026, 4:12 PM +1000) 9842e2ed 0ff606e7

+19 -31
+18 -30
lib/mod.ts
··· 1 1 function walk(input: any, seen: any[]): string | undefined { 2 2 if (input === null) return 'L'; 3 3 4 - let type = typeof input; 5 - if (type !== 'object') { 6 - if (type === 'number') return input - input === 0 ? 'n' + input : 'L'; 7 - if (type === 'string') return 's' + input; 8 - if (type === 'bigint') return 'n' + input; 9 - if (type === 'boolean') return input ? 'T' : 'F'; 10 - // functions, symbols, undefined are dropped and treated as identity equal. 4 + let out: string, i = 0, keys: any, tmp: any; 5 + 6 + if ((tmp = typeof input) !== 'object') { 7 + if (tmp === 'number') return input - input === 0 ? 'n' + input : 'L'; 8 + if (tmp === 'string') return 's' + input; 9 + if (tmp === 'bigint') return 'n' + input; 10 + if (tmp === 'boolean') return input ? 'T' : 'F'; 11 11 return; 12 12 } 13 13 14 - // Arrays are the most common container, so settle them before the rarer 15 - // Date/RegExp leaves to keep the hot path short. 16 14 let is_arr = Array.isArray(input); 17 15 if (!is_arr) { 18 16 if (input instanceof Date) return 'd' + +input; 19 17 if (input instanceof RegExp) return 'r' + input.source + input.flags; 18 + 20 19 if (typeof input.toJSON === 'function' && !ArrayBuffer.isView(input)) { 21 20 input = input.toJSON(); 22 - if (typeof input !== 'object' || input === null) return walk(input, seen); 21 + if (input === null) return 'L'; 22 + if (typeof input !== 'object') return walk(input, seen); 23 23 is_arr = Array.isArray(input); 24 24 } 25 25 } 26 26 27 - let ref: any = seen.indexOf(input); 28 - if (~ref) return '~' + (ref + 1); 27 + tmp = seen.indexOf(input); 28 + if (~tmp) return '~' + (tmp + 1); 29 29 seen.push(input); 30 30 31 - let out: string, i = 0, keys: any, tmp: any; 32 - 33 31 if (is_arr) { 34 32 for ( 35 33 out = 'a'; ··· 40 38 out = 'e'; 41 39 for (let value of input) out += (tmp = walk(value, seen)) === undefined ? 'L' : tmp; 42 40 } else if (input instanceof Map) { 43 - out = 'o'; 44 - if (input.size > 1) { 45 - for (keys = [...input.keys()].sort(); i < keys.length; i++) { 46 - (tmp = walk(input.get(keys[i]), seen)) !== undefined && (out += keys[i] + tmp); 47 - } 48 - } else { 49 - for (keys of input) { 50 - (tmp = walk(keys[1], seen)) !== undefined && (out += keys[0] + tmp); 51 - } 41 + keys = [...input.keys()]; 42 + if (keys.length > 1) keys.sort(); 43 + for (out = 'o'; i < keys.length; i++) { 44 + if ((tmp = walk(input.get(keys[i]), seen)) !== undefined) out += keys[i] + tmp; 52 45 } 53 - } // Plain objects, class instances and null-prototype objects have no 54 - // `Symbol.toStringTag`. Typed arrays (and other ArrayBuffer views) do, but 55 - // JSON treats them as index-keyed objects, so we walk their keys the same way. 56 - // Other exotic builtins (Promise, WeakMap, ArrayBuffer) fall through to the throw. 57 - else if (input[Symbol.toStringTag] === undefined || ArrayBuffer.isView(input)) { 58 - out = 'o'; 46 + } else if (input[Symbol.toStringTag] === undefined || ArrayBuffer.isView(input)) { 59 47 keys = Object.keys(input); 60 48 if (keys.length > 1) keys.sort(); 61 - for (; i < keys.length; i++) { 49 + for (out = 'o'; i < keys.length; i++) { 62 50 if ((tmp = walk(input[keys[i]], seen)) !== undefined) out += keys[i] + tmp; 63 51 } 64 52 } else {
+1 -1
readme.md
··· 28 28 - 🧬 **Canonical.** The same shape always produces the same id. 29 29 - 🌀 **Deep and cycle-safe.** Handles nested objects, arrays, sets, maps, and circular references. 30 30 - 🏎 **Fast.** See the [benchmarks](#-benchmark). 31 - - 🪶 **Tiny.** Around 571B minified and gzipped, with zero 31 + - 🪶 **Tiny.** Around 535B minified and gzipped, with zero 32 32 [dependencies](https://npm.anvaka.com/#/view/2d/object-identity/). 33 33 34 34 ## ⚙️ Install