1
- interface IOptions {
2
- strict : boolean ;
3
- }
1
+ import { v3 as hash } from 'murmurhash'
4
2
5
- const makeCopy = ( jsonValue : any ) => {
6
- let copyJson : any ;
3
+ const EMPTY_OBJECT = '__empty__obj'
4
+ const EMPTY_ARRAY = '__empty__arr'
7
5
8
- if ( Array . isArray ( jsonValue ) ) {
9
- copyJson = [ ...jsonValue ]
10
- } else {
11
- copyJson = { ...jsonValue }
6
+ class Node {
7
+ public children : any
8
+ public hash : number
9
+ constructor ( ) {
10
+ this . children = [ ]
11
+ this . hash = 0
12
12
}
13
-
14
- return copyJson
15
13
}
16
14
17
- const atLeastSatisfySource = ( objSource : any , objDest : any , strict : boolean = false ) => {
18
- let satisfied : boolean = true
19
-
20
- if ( typeof objSource === 'object' ) {
21
- // handle json object
22
- for ( const key of Object . keys ( objSource ) ) {
23
- if ( strict ) {
24
- satisfied = satisfied && objSource [ key ] === objDest [ key ]
25
- } else {
26
- // tslint:disable-next-line: triple-equals
27
- satisfied = satisfied && objSource [ key ] == objDest [ key ]
28
- }
29
- }
30
- } else {
31
- // handle primitive type
32
- if ( strict ) {
33
- satisfied = satisfied && objSource === objDest
34
- } else {
35
- // tslint:disable-next-line: triple-equals
36
- satisfied = satisfied && objSource == objDest
37
- }
38
- }
39
-
40
- return satisfied
15
+ const hasher = ( thing : any , prefix : string = '' ) => {
16
+ const stringThing = prefix + ( typeof thing ) + '::' + thing
17
+ return hash ( stringThing )
41
18
}
42
19
43
- const compareObject = ( objMaster : any , objToCompare : any , opt ?: IOptions ) : boolean => {
44
- let satisfied : boolean = true ;
45
-
46
- if ( Array . isArray ( objMaster ) ) {
47
- satisfied = satisfied && compareArray ( objMaster , objToCompare , opt ) ;
48
- } else {
49
- satisfied = satisfied && atLeastSatisfySource ( objMaster , objToCompare , opt ?. strict )
50
- }
51
-
52
- return satisfied ;
20
+ const combineHashes = ( parentHash : any , child : any ) => {
21
+ /* tslint:disable:no-bitwise */
22
+ return ( parentHash + child . hash ) & 0xFFFFFFFF
53
23
}
54
24
55
- const compareArray = ( jsonArrMaster : any , jsonArrToCompare : any , opt ?: IOptions ) : boolean => {
56
- const arrComparison : any = makeCopy ( jsonArrToCompare )
57
- let satisfied : boolean = true ;
58
- for ( const key of Object . keys ( jsonArrMaster ) ) {
59
- // check key is number
60
- const isKeyNumber = Number . NaN !== Number ( key )
25
+ const newNode = ( thing : any , prefix : string ) => {
26
+ const node = new Node ( )
27
+ node . hash = hasher ( thing , prefix )
28
+ return node
29
+ }
61
30
62
- if ( isKeyNumber ) {
63
- let foundSatisfy = false
64
- for ( const keyOnComp of Object . keys ( arrComparison ) ) {
65
- foundSatisfy = compareObject ( jsonArrMaster [ key ] , arrComparison [ keyOnComp ] )
31
+ const createTree = ( currNode : Node , currentInput : any , prefix : string = '' ) => {
32
+ const isObject = typeof currentInput === 'object'
33
+ const isNil = currentInput === null || typeof currentInput === 'undefined'
34
+ const isArray = Array . isArray ( currentInput )
35
+ const keys : any = ! isNil ? Object . keys ( currentInput ) : [ ]
66
36
67
- if ( foundSatisfy ) {
68
- foundSatisfy = true
69
- delete arrComparison [ keyOnComp ]
70
- break
71
- }
72
- }
73
- satisfied = satisfied && foundSatisfy
74
- } else {
75
- satisfied = satisfied && compareObject ( jsonArrMaster [ key ] , arrComparison [ key ] , opt ) ;
76
- delete arrComparison [ key ]
77
- }
37
+ // we're at a weird value
38
+ if ( currentInput instanceof Date || currentInput instanceof RegExp ) {
39
+ return newNode ( currentInput , prefix )
78
40
}
79
- return satisfied
80
- }
81
41
82
- export const compare = ( jsonMaster : any , jsonToCompare : any , opt ?: IOptions ) : boolean => {
83
- if ( ! jsonMaster || ! jsonToCompare ) return false
42
+ // if we're at a value
43
+ if ( ! isObject && ! isArray ) {
44
+ return newNode ( currentInput , prefix )
45
+ }
84
46
85
- if ( Array . isArray ( jsonMaster ) || Array . isArray ( jsonToCompare ) ) {
86
- if ( Array . isArray ( jsonMaster ) && ! Array . isArray ( jsonToCompare ) ||
87
- ! Array . isArray ( jsonMaster ) && Array . isArray ( jsonToCompare ) ) {
88
- return false
47
+ // if we're at an iterable
48
+ if ( ! keys . length ) {
49
+ return isArray ? newNode ( EMPTY_ARRAY , prefix ) : newNode ( EMPTY_OBJECT , prefix )
50
+ }
51
+ for ( const key of keys ) {
52
+ let pfx
53
+ if ( ! isArray && isObject ) { // if we're dealing with an object prefix the key
54
+ pfx = key
89
55
}
56
+
57
+ const node = createTree ( new Node ( ) , currentInput [ key ] , pfx )
58
+ currNode . children . push ( node )
90
59
}
60
+ // iterable's hash is combined hash of all children
61
+ const combined = currNode . children . reduce ( combineHashes , 0 )
62
+ currNode . hash = hasher ( combined )
63
+ return currNode
64
+ }
91
65
92
- let satisfied : boolean = true ;
66
+ const createFinalHash = ( input : any ) => {
67
+ const tree = createTree ( new Node ( ) , input )
68
+ return tree . hash
69
+ }
93
70
94
- if ( Array . isArray ( jsonMaster ) ) {
95
- satisfied = satisfied && compareArray ( jsonMaster , jsonToCompare , opt ) ;
96
- } else {
97
- satisfied = satisfied && compareObject ( jsonMaster , jsonToCompare , opt ) ;
71
+ export const compare = ( a : any , b : any ) => {
72
+ if ( a && b && ( ( Array . isArray ( a ) && Array . isArray ( b ) ) || ( typeof a === 'object' && typeof b === 'object' ) ) && ( a . length === b . length ) ) {
73
+ return createFinalHash ( a ) === createFinalHash ( b )
98
74
}
99
-
100
- return satisfied ;
101
- } ;
75
+ return false
76
+ }
0 commit comments