1
+ import { isVueInstance } from '../core/component/state/is'
2
+ import { Replacer } from '../core/component/state/replacer'
3
+
1
4
const MAX_SERIALIZED_SIZE = 2 * 1024 * 1024 // 2MB
2
5
3
- function encode ( data : Record < string , unknown > , replacer : ( ( this : any , key : string , value : any ) => any ) | null , list : unknown [ ] , seen : Map < unknown , number > ) {
4
- let stored , key , value , i , l
6
+ function isObject ( _data : unknown , proto : string ) : _data is Record < string , unknown > {
7
+ return proto === '[object Object]'
8
+ }
9
+
10
+ function isArray ( _data : unknown , proto : string ) : _data is unknown [ ] {
11
+ return proto === '[object Array]'
12
+ }
13
+
14
+ /**
15
+ * This function is used to serialize object with handling circular references.
16
+ *
17
+ * ```ts
18
+ * const obj = { a: 1, b: { c: 2 }, d: obj }
19
+ * const result = stringifyCircularAutoChunks(obj) // call `encode` inside
20
+ * console.log(result) // [{"a":1,"b":2,"d":0},1,{"c":4},2]
21
+ * ```
22
+ *
23
+ * Each object is stored in a list and the index is used to reference the object.
24
+ * With seen map, we can check if the object is already stored in the list to avoid circular references.
25
+ *
26
+ * Note: here we have a special case for Vue instance.
27
+ * We check if a vue instance includes itself in its properties and skip it
28
+ * by using `seenVueInstance` and `depth` to avoid infinite loop.
29
+ */
30
+ function encode ( data : unknown , replacer : Replacer | null , list : unknown [ ] , seen : Map < unknown , number > , depth = 0 , seenVueInstance = new Map < any , number > ( ) ) : number {
31
+ let stored : Record < string , number > | number [ ]
32
+ let key : string
33
+ let value : unknown
34
+ let i : number
35
+ let l : number
36
+
5
37
const seenIndex = seen . get ( data )
6
38
if ( seenIndex != null )
7
39
return seenIndex
8
40
9
41
const index = list . length
10
42
const proto = Object . prototype . toString . call ( data )
11
- if ( proto === '[object Object]' ) {
43
+ if ( isObject ( data , proto ) ) {
12
44
stored = { }
13
45
seen . set ( data , index )
14
46
list . push ( stored )
15
47
const keys = Object . keys ( data )
16
48
for ( i = 0 , l = keys . length ; i < l ; i ++ ) {
17
49
key = keys [ i ]
50
+ value = data [ key ]
51
+ const isVm = value != null && isObject ( value , Object . prototype . toString . call ( data ) ) && isVueInstance ( value )
18
52
try {
19
53
// fix vue warn for compilerOptions passing-options-to-vuecompiler-sfc
20
54
// @TODO : need to check if it will cause any other issues
21
55
if ( key === 'compilerOptions' )
22
- return
23
- value = data [ key ]
24
- if ( replacer )
25
- value = replacer . call ( data , key , value )
56
+ return index
57
+ if ( replacer ) {
58
+ value = replacer . call ( data , key , value , depth , seenVueInstance )
59
+ }
26
60
}
27
61
catch ( e ) {
28
62
value = e
29
63
}
30
- stored [ key ] = encode ( value , replacer , list , seen )
64
+ stored [ key ] = encode ( value , replacer , list , seen , depth + 1 , seenVueInstance )
65
+ // delete vue instance if its properties have been processed
66
+ if ( isVm ) {
67
+ seenVueInstance . delete ( value )
68
+ }
31
69
}
32
70
}
33
- else if ( proto === '[object Array]' ) {
71
+ else if ( isArray ( data , proto ) ) {
34
72
stored = [ ]
35
73
seen . set ( data , index )
36
74
list . push ( stored )
37
75
for ( i = 0 , l = data . length ; i < l ; i ++ ) {
38
76
try {
39
77
value = data [ i ]
40
78
if ( replacer )
41
- value = replacer . call ( data , i , value )
79
+ value = replacer . call ( data , i , value , depth , seenVueInstance )
42
80
}
43
81
catch ( e ) {
44
82
value = e
45
83
}
46
- stored [ i ] = encode ( value , replacer , list , seen )
84
+ stored [ i ] = encode ( value , replacer , list , seen , depth + 1 , seenVueInstance )
47
85
}
48
86
}
49
87
else {
@@ -79,14 +117,16 @@ function decode(list: unknown[] | string, reviver: ((this: any, key: string, val
79
117
}
80
118
}
81
119
82
- export function stringifyCircularAutoChunks ( data : Record < string , unknown > , replacer : ( ( this : any , key : string , value : any ) => any ) | null = null , space : number | null = null ) {
120
+ export function stringifyCircularAutoChunks ( data : Record < string , unknown > , replacer : Replacer | null = null , space : number | null = null ) {
83
121
let result : string
84
122
try {
123
+ // no circular references, JSON.stringify can handle this
85
124
result = arguments . length === 1
86
125
? JSON . stringify ( data )
87
- : JSON . stringify ( data , replacer ! , space ! )
126
+ : JSON . stringify ( data , ( k , v ) => replacer ?. ( k , v ) , space ! )
88
127
}
89
128
catch ( e ) {
129
+ // handle circular references
90
130
result = stringifyStrictCircularAutoChunks ( data , replacer ! , space ! )
91
131
}
92
132
if ( result . length > MAX_SERIALIZED_SIZE ) {
@@ -100,7 +140,7 @@ export function stringifyCircularAutoChunks(data: Record<string, unknown>, repla
100
140
return result
101
141
}
102
142
103
- export function stringifyStrictCircularAutoChunks ( data : Record < string , unknown > , replacer : ( ( this : any , key : string , value : any ) => any ) | null = null , space : number | null = null ) {
143
+ export function stringifyStrictCircularAutoChunks ( data : Record < string , unknown > , replacer : Replacer | null = null , space : number | null = null ) {
104
144
const list = [ ]
105
145
encode ( data , replacer , list , new Map ( ) )
106
146
return space
0 commit comments