@@ -140,9 +140,20 @@ function deserializeValue(value: any, options: EJSON.Options = {}) {
140
140
return value ;
141
141
}
142
142
143
+ type EJSONSerializeOptions = EJSON . Options & {
144
+ seenObjects : { obj : unknown ; propertyName : string } [ ] ;
145
+ } ;
146
+
143
147
// eslint-disable-next-line @typescript-eslint/no-explicit-any
144
- function serializeArray ( array : any [ ] , options : EJSON . Options ) : any [ ] {
145
- return array . map ( ( v : unknown ) => serializeValue ( v , options ) ) ;
148
+ function serializeArray ( array : any [ ] , options : EJSONSerializeOptions ) : any [ ] {
149
+ return array . map ( ( v : unknown , index : number ) => {
150
+ options . seenObjects . push ( { propertyName : `index ${ index } ` , obj : null } ) ;
151
+ try {
152
+ return serializeValue ( v , options ) ;
153
+ } finally {
154
+ options . seenObjects . pop ( ) ;
155
+ }
156
+ } ) ;
146
157
}
147
158
148
159
function getISOString ( date : Date ) {
@@ -152,7 +163,37 @@ function getISOString(date: Date) {
152
163
}
153
164
154
165
// eslint-disable-next-line @typescript-eslint/no-explicit-any
155
- function serializeValue ( value : any , options : EJSON . Options ) : any {
166
+ function serializeValue ( value : any , options : EJSONSerializeOptions ) : any {
167
+ if ( ( typeof value === 'object' || typeof value === 'function' ) && value !== null ) {
168
+ const index = options . seenObjects . findIndex ( entry => entry . obj === value ) ;
169
+ if ( index !== - 1 ) {
170
+ const props = options . seenObjects . map ( entry => entry . propertyName ) ;
171
+ const leadingPart = props
172
+ . slice ( 0 , index )
173
+ . map ( prop => `${ prop } -> ` )
174
+ . join ( '' ) ;
175
+ const alreadySeen = props [ index ] ;
176
+ const circularPart =
177
+ ' -> ' +
178
+ props
179
+ . slice ( index + 1 , props . length - 1 )
180
+ . map ( prop => `${ prop } -> ` )
181
+ . join ( '' ) ;
182
+ const current = props [ props . length - 1 ] ;
183
+ const leadingSpace = ' ' . repeat ( leadingPart . length + alreadySeen . length / 2 ) ;
184
+ const dashes = '-' . repeat (
185
+ circularPart . length + ( alreadySeen . length + current . length ) / 2 - 1
186
+ ) ;
187
+
188
+ throw new TypeError (
189
+ 'Converting circular structure to EJSON:\n' +
190
+ ` ${ leadingPart } ${ alreadySeen } ${ circularPart } ${ current } \n` +
191
+ ` ${ leadingSpace } \\${ dashes } /`
192
+ ) ;
193
+ }
194
+ options . seenObjects [ options . seenObjects . length - 1 ] . obj = value ;
195
+ }
196
+
156
197
if ( Array . isArray ( value ) ) return serializeArray ( value , options ) ;
157
198
158
199
if ( value === undefined ) return null ;
@@ -232,15 +273,20 @@ const BSON_TYPE_MAPPINGS = {
232
273
} as const ;
233
274
234
275
// eslint-disable-next-line @typescript-eslint/no-explicit-any
235
- function serializeDocument ( doc : any , options : EJSON . Options ) {
276
+ function serializeDocument ( doc : any , options : EJSONSerializeOptions ) {
236
277
if ( doc == null || typeof doc !== 'object' ) throw new Error ( 'not an object instance' ) ;
237
278
238
279
const bsontype : BSONType [ '_bsontype' ] = doc . _bsontype ;
239
280
if ( typeof bsontype === 'undefined' ) {
240
281
// It's a regular object. Recursively serialize its property values.
241
282
const _doc : Document = { } ;
242
283
for ( const name in doc ) {
243
- _doc [ name ] = serializeValue ( doc [ name ] , options ) ;
284
+ options . seenObjects . push ( { propertyName : name , obj : null } ) ;
285
+ try {
286
+ _doc [ name ] = serializeValue ( doc [ name ] , options ) ;
287
+ } finally {
288
+ options . seenObjects . pop ( ) ;
289
+ }
244
290
}
245
291
return _doc ;
246
292
} else if ( isBSONType ( doc ) ) {
@@ -365,9 +411,11 @@ export namespace EJSON {
365
411
replacer = undefined ;
366
412
space = 0 ;
367
413
}
368
- options = Object . assign ( { } , { relaxed : true , legacy : false } , options ) ;
414
+ const serializeOptions = Object . assign ( { relaxed : true , legacy : false } , options , {
415
+ seenObjects : [ { propertyName : '(root)' , obj : null } ]
416
+ } ) ;
369
417
370
- const doc = serializeValue ( value , options ) ;
418
+ const doc = serializeValue ( value , serializeOptions ) ;
371
419
return JSON . stringify ( doc , replacer as Parameters < JSON [ 'stringify' ] > [ 1 ] , space ) ;
372
420
}
373
421
0 commit comments