1
- import { isPrimitive , isSyntheticEvent } from './is' ;
1
+ import { Primitive } from '@sentry/types' ;
2
+
3
+ import { isNaN , isSyntheticEvent } from './is' ;
2
4
import { memoBuilder , MemoFunc } from './memo' ;
3
5
import { convertToPlainObject } from './object' ;
4
6
import { getFunctionName } from './stacktrace' ;
@@ -28,7 +30,7 @@ type Prototype = { constructor: (...args: unknown[]) => unknown };
28
30
export function normalize ( input : unknown , depth : number = + Infinity , maxProperties : number = + Infinity ) : any {
29
31
try {
30
32
// since we're at the outermost level, there is no key
31
- return walk ( '' , input as UnknownMaybeWithToJson , depth , maxProperties ) ;
33
+ return visit ( '' , input as UnknownMaybeWithToJson , depth , maxProperties ) ;
32
34
} catch ( _oO ) {
33
35
return '**non-serializable**' ;
34
36
}
@@ -52,80 +54,97 @@ export function normalizeToSize<T>(
52
54
}
53
55
54
56
/**
55
- * Walks an object to perform a normalization on it
57
+ * Visits a node to perform a normalization on it
56
58
*
57
- * @param key of object that's walked in current iteration
58
- * @param value object to be walked
59
+ * @param key The key corresponding to the given node
60
+ * @param value The node to be visited
59
61
* @param depth Optional number indicating how deep should walking be performed
60
- * @param maxProperties Optional maximum number of properties/elements included in any single object/array
62
+ * @param maxProperties Optional maximum number of properties/elements included in any single object/array
61
63
* @param memo Optional Memo class handling decycling
62
64
*/
63
- export function walk (
65
+ export function visit (
64
66
key : string ,
65
- value : UnknownMaybeWithToJson ,
67
+ value : unknown ,
66
68
depth : number = + Infinity ,
67
69
maxProperties : number = + Infinity ,
68
70
memo : MemoFunc = memoBuilder ( ) ,
69
- ) : unknown {
71
+ ) : Primitive | unknown [ ] | { [ key : string ] : unknown } {
70
72
const [ memoize , unmemoize ] = memo ;
71
73
72
- // If we reach the maximum depth, serialize whatever is left
73
- if ( depth === 0 ) {
74
- return stringifyValue ( key , value ) ;
74
+ // if the value has a `toJSON` method, bail and let it do the work
75
+ const valueWithToJSON = value as unknown & { toJSON ?: ( ) => string } ;
76
+ if ( valueWithToJSON && typeof valueWithToJSON . toJSON === 'function' ) {
77
+ try {
78
+ return valueWithToJSON . toJSON ( ) ;
79
+ } catch ( err ) {
80
+ return `**non-serializable** (${ err } )` ;
81
+ }
82
+ }
83
+
84
+ // get the simple cases out of the way first
85
+ if ( value === null || ( [ 'number' , 'boolean' , 'string' ] . includes ( typeof value ) && ! isNaN ( value ) ) ) {
86
+ return value as Primitive ;
75
87
}
76
88
77
- // If value implements `toJSON` method, call it and return early
78
- if ( value !== null && value !== undefined && typeof value . toJSON === 'function' ) {
79
- return value . toJSON ( ) ;
89
+ const stringified = stringifyValue ( key , value ) ;
90
+
91
+ // Anything we could potentially dig into more (objects or arrays) will have come back as `"[object XXXX]"`.
92
+ // Everything else will have already been serialized, so if we don't see that pattern, we're done.
93
+ if ( ! stringified . startsWith ( '[object ' ) ) {
94
+ return stringified ;
80
95
}
81
96
82
- // `makeSerializable` provides a string representation of certain non-serializable values. For all others, it's a
83
- // pass-through. If what comes back is a primitive (either because it's been stringified or because it was primitive
84
- // all along), we're done.
85
- const serializable = stringifyValue ( key , value ) ;
86
- if ( isPrimitive ( serializable ) ) {
87
- return serializable ;
97
+ // we're also done if we've reached the max depth
98
+ if ( depth === 0 ) {
99
+ // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
100
+ return stringified . replace ( 'object ' , '' ) ;
88
101
}
89
102
90
- // Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
91
- // with extracted key:value pairs) or the input itself.
103
+ // Create source that we will use for the next iteration. Because not all of the properties we care about on `Error`
104
+ // and `Event` instances are ennumerable, we first convert those to plain objects. (`convertToPlainObject` is a
105
+ // pass-through for everything else.)
92
106
const source = convertToPlainObject ( value ) ;
93
107
94
- // Create an accumulator that will act as a parent for all future itterations of that branch
108
+ // Create an accumulator that will act as a parent for all future iterations of this branch, and keep track of the
109
+ // number of properties/entries we add to it
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
111
const acc : { [ key : string ] : any } = Array . isArray ( value ) ? [ ] : { } ;
112
+ let numAdded = 0 ;
96
113
97
- // If we already walked that branch, bail out, as it's circular reference
114
+ // If we've already visited this branch, bail out, as it's circular reference
98
115
if ( memoize ( value ) ) {
99
116
return '[Circular ~]' ;
100
117
}
101
118
102
- let propertyCount = 0 ;
103
- // Walk all keys of the source
119
+ // visit all keys of the source
104
120
for ( const innerKey in source ) {
105
121
// Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
106
122
if ( ! Object . prototype . hasOwnProperty . call ( source , innerKey ) ) {
107
123
continue ;
108
124
}
109
125
110
- if ( propertyCount >= maxProperties ) {
126
+ if ( numAdded >= maxProperties ) {
111
127
acc [ innerKey ] = '[MaxProperties ~]' ;
112
128
break ;
113
129
}
114
130
115
- propertyCount += 1 ;
131
+ // Recursively visit all the child nodes
132
+ const innerValue : unknown = source [ innerKey ] ;
133
+ acc [ innerKey ] = visit ( innerKey , innerValue , depth - 1 , maxProperties , memo ) ;
116
134
117
- // Recursively walk through all the child nodes
118
- const innerValue = source [ innerKey ] as UnknownMaybeWithToJson ;
119
- acc [ innerKey ] = walk ( innerKey , innerValue , depth - 1 , maxProperties , memo ) ;
135
+ numAdded += 1 ;
120
136
}
121
137
122
- // Once walked through all the branches, remove the parent from memo storage
138
+ // Once we've visited all the branches, remove the parent from memo storage
123
139
unmemoize ( value ) ;
124
140
125
141
// Return accumulated values
126
142
return acc ;
127
143
}
128
144
145
+ // TODO remove this in v7 (we don't use it anywhere, but it's a public method)
146
+ export { visit as walk } ;
147
+
129
148
/**
130
149
* Stringify the given value. Handles various known special values and types.
131
150
*
0 commit comments