@@ -22,11 +22,19 @@ function serializer(replacer: any, cycleReplacer: any) {
2222
2323 if ( cycleReplacer === undefined )
2424 cycleReplacer = function ( key : any , value : any ) {
25+ if ( value ?. [ "x-circular-ref" ] ) {
26+ return value . title ? `circular(${ value . title } )` : "circular()" ;
27+ }
2528 if ( stack [ 0 ] === value ) return "circular()" ;
2629 return value . title ? `circular(${ value . title } )` : "circular()" ;
2730 } ;
2831
2932 return function ( key : any , value : any ) {
33+ // Convert objects flagged as circular
34+ if ( value ?. [ "x-circular-ref" ] ) {
35+ return value . title ? `circular(${ value . title } )` : "circular()" ;
36+ }
37+
3038 // Resolve discriminator ref pointers
3139 if ( value ?. discriminator !== undefined ) {
3240 const parser = new OpenAPIParser ( stack [ 0 ] ) ;
@@ -115,6 +123,49 @@ async function resolveJsonRefs(specUrlOrObject: object | string) {
115123 }
116124}
117125
126+ function markCircularRefs ( obj : any , stack : any [ ] = [ ] ) : boolean {
127+ if ( ! obj || typeof obj !== "object" ) {
128+ return false ;
129+ }
130+
131+ const pos = stack . indexOf ( obj ) ;
132+ if ( pos !== - 1 ) {
133+ for ( let i = pos + 1 ; i < stack . length ; i ++ ) {
134+ const cyc = stack [ i ] ;
135+ if ( cyc && typeof cyc === "object" ) {
136+ cyc [ "x-circular-ref" ] = true ;
137+ }
138+ }
139+ return true ;
140+ }
141+
142+ stack . push ( obj ) ;
143+ let foundCircular = false ;
144+
145+ if ( Array . isArray ( obj ) ) {
146+ for ( let i = 0 ; i < obj . length ; i ++ ) {
147+ const val = obj [ i ] ;
148+ if ( typeof val === "object" && val !== null ) {
149+ if ( markCircularRefs ( val , stack ) ) {
150+ foundCircular = true ;
151+ }
152+ }
153+ }
154+ } else {
155+ for ( const key of Object . keys ( obj ) ) {
156+ const val = obj [ key ] ;
157+ if ( typeof val === "object" && val !== null ) {
158+ if ( markCircularRefs ( val , stack ) ) {
159+ foundCircular = true ;
160+ }
161+ }
162+ }
163+ }
164+
165+ stack . pop ( ) ;
166+ return foundCircular ;
167+ }
168+
118169export async function loadAndResolveSpec ( specUrlOrObject : object | string ) {
119170 const config = new Config ( { } as ResolvedConfig ) ;
120171 const bundleOpts = {
@@ -154,6 +205,9 @@ export async function loadAndResolveSpec(specUrlOrObject: object | string) {
154205
155206 const resolved = await resolveJsonRefs ( parsed ) ;
156207
208+ // Mark any remaining circular references
209+ markCircularRefs ( resolved ) ;
210+
157211 // Force serialization and replace circular $ref pointers
158212 // @ts -ignore
159213 const serialized = JSON . stringify ( resolved , serializer ( ) ) ;
0 commit comments