@@ -9,6 +9,7 @@ import ParsePolygon from './ParsePolygon';
9
9
import ParseObject from './ParseObject' ;
10
10
import { Op } from './ParseOp' ;
11
11
import ParseRelation from './ParseRelation' ;
12
+ import { cyrb53 } from './CryptoUtils' ;
12
13
13
14
const MAX_RECURSIVE_CALLS = 999 ;
14
15
@@ -18,18 +19,15 @@ function encode(
18
19
forcePointers : boolean ,
19
20
seen : Array < mixed > ,
20
21
offline : boolean ,
21
- counter : number = 0
22
+ counter : number ,
23
+ initialValue : mixed
22
24
) : any {
23
25
counter ++ ;
24
26
25
27
if ( counter > MAX_RECURSIVE_CALLS ) {
26
28
const message = 'Encoding object failed due to high number of recursive calls, likely caused by circular reference within object.' ;
27
29
console . error ( message ) ;
28
- console . error ( 'Value causing potential infinite recursion:' , value ) ;
29
- console . error ( 'Disallow objects:' , disallowObjects ) ;
30
- console . error ( 'Force pointers:' , forcePointers ) ;
31
- console . error ( 'Seen:' , seen ) ;
32
- console . error ( 'Offline:' , offline ) ;
30
+ console . error ( 'Value causing potential infinite recursion:' , initialValue ) ;
33
31
34
32
throw new Error ( message ) ;
35
33
}
@@ -38,73 +36,90 @@ function encode(
38
36
if ( disallowObjects ) {
39
37
throw new Error ( 'Parse Objects not allowed here' ) ;
40
38
}
41
- const seenEntry = value . id ? value . className + ':' + value . id : value ;
39
+ const entryIdentifier = value . id ? value . className + ':' + value . id : value ;
42
40
if (
43
41
forcePointers ||
44
- ! seen ||
45
- seen . indexOf ( seenEntry ) > - 1 ||
42
+ seen . includes ( entryIdentifier ) ||
46
43
value . dirty ( ) ||
47
- Object . keys ( value . _getServerData ( ) ) . length < 1
44
+ Object . keys ( value . _getServerData ( ) ) . length === 0
48
45
) {
49
46
if ( offline && value . _getId ( ) . startsWith ( 'local' ) ) {
50
47
return value . toOfflinePointer ( ) ;
51
48
}
52
49
return value . toPointer ( ) ;
53
50
}
54
- seen = seen . concat ( seenEntry ) ;
51
+ seen . push ( entryIdentifier ) ;
55
52
return value . _toFullJSON ( seen , offline ) ;
56
- }
57
- if (
53
+ } else if (
58
54
value instanceof Op ||
59
55
value instanceof ParseACL ||
60
56
value instanceof ParseGeoPoint ||
61
57
value instanceof ParsePolygon ||
62
58
value instanceof ParseRelation
63
59
) {
64
60
return value . toJSON ( ) ;
65
- }
66
- if ( value instanceof ParseFile ) {
61
+ } else if ( value instanceof ParseFile ) {
67
62
if ( ! value . url ( ) ) {
68
63
throw new Error ( 'Tried to encode an unsaved file.' ) ;
69
64
}
70
65
return value . toJSON ( ) ;
71
- }
72
- if ( Object . prototype . toString . call ( value ) === '[object Date]' ) {
66
+ } else if ( Object . prototype . toString . call ( value ) === '[object Date]' ) {
73
67
if ( isNaN ( value ) ) {
74
68
throw new Error ( 'Tried to encode an invalid date.' ) ;
75
69
}
76
70
return { __type : 'Date' , iso : ( value : any ) . toJSON ( ) } ;
77
- }
78
- if (
71
+ } else if (
79
72
Object . prototype . toString . call ( value ) === '[object RegExp]' &&
80
73
typeof value . source === 'string'
81
74
) {
82
75
return value . source ;
83
- }
84
-
85
- if ( Array . isArray ( value ) ) {
86
- return value . map ( v => {
87
- return encode ( v , disallowObjects , forcePointers , seen , offline , counter ) ;
88
- } ) ;
89
- }
90
-
91
- if ( value && typeof value === 'object' ) {
76
+ } else if ( Array . isArray ( value ) ) {
77
+ return value . map ( v => encode ( v , disallowObjects , forcePointers , seen , offline , counter , initialValue ) ) ;
78
+ } else if ( value && typeof value === 'object' ) {
92
79
const output = { } ;
93
80
for ( const k in value ) {
94
- output [ k ] = encode ( value [ k ] , disallowObjects , forcePointers , seen , offline , counter ) ;
81
+ try {
82
+ // Attempts to get the name of the object's constructor
83
+ // Ref: https://stackoverflow.com/a/332429/6456163
84
+ const name = value [ k ] . name || value [ k ] . constructor . name ;
85
+ if ( name && name != "undefined" ) {
86
+ if ( seen . includes ( name ) ) {
87
+ output [ k ] = value [ k ] ;
88
+ continue ;
89
+ } else {
90
+ seen . push ( name ) ;
91
+ }
92
+ }
93
+ } catch ( e ) {
94
+ // Support anonymous functions by hashing the function body,
95
+ // preventing infinite recursion in the case of circular references
96
+ if ( value [ k ] instanceof Function ) {
97
+ const funcString = value [ k ] . toString ( ) ;
98
+ if ( seen . includes ( funcString ) ) {
99
+ output [ k ] = value [ k ] ;
100
+ continue ;
101
+ } else {
102
+ const hash = cyrb53 ( funcString ) ;
103
+ seen . push ( hash ) ;
104
+ }
105
+ }
106
+ }
107
+ output [ k ] = encode ( value [ k ] , disallowObjects , forcePointers , seen , offline , counter , initialValue ) ;
95
108
}
96
109
return output ;
110
+ } else {
111
+ return value ;
97
112
}
98
-
99
- return value ;
100
113
}
101
114
102
115
export default function (
103
116
value : mixed ,
104
117
disallowObjects ?: boolean ,
105
118
forcePointers ?: boolean ,
106
119
seen ?: Array < mixed > ,
107
- offline ?: boolean
120
+ offline ?: boolean ,
121
+ counter ?: number ,
122
+ initialValue ?: mixed
108
123
) : any {
109
- return encode ( value , ! ! disallowObjects , ! ! forcePointers , seen || [ ] , offline , 0 ) ;
124
+ return encode ( value , ! ! disallowObjects , ! ! forcePointers , seen || [ ] , ! ! offline , counter || 0 , initialValue || value ) ;
110
125
}
0 commit comments