@@ -193,7 +193,7 @@ function getResolvedPropertyName(refPropertyName) {
193193}
194194
195195// Helper to resolve a single reference
196- function resolveSingleReference ( ref , level , isRecursive , currentDepth = 0 ) {
196+ function resolveSingleReference ( ref , level , isRecursive , currentDepth = 0 , visited = new Set ( ) ) {
197197 if ( ! ref || typeof ref !== 'object' ) return ref ;
198198
199199 const refId = extractId ( ref ) ;
@@ -211,7 +211,8 @@ function resolveSingleReference(ref, level, isRecursive, currentDepth = 0) {
211211 if ( resolvedObj ) {
212212 // If recursive, resolve all references in the resolved object
213213 // Pass currentDepth + 1 to continue recursive resolution
214- return isRecursive ? resolveReferences ( resolvedObj , level , currentDepth + 1 ) : resolvedObj ;
214+ // Also pass the visited set to prevent infinite loops
215+ return isRecursive ? resolveReferences ( resolvedObj , level , currentDepth + 1 , visited ) : resolvedObj ;
215216 }
216217
217218 return ref ;
@@ -220,9 +221,30 @@ function resolveSingleReference(ref, level, isRecursive, currentDepth = 0) {
220221// Helper to resolve references in an object (truly recursive and generic)
221222// level: 'none' (default), 'children' (first level only), 'all' (recursive)
222223// startDepth: starting depth for recursive processing (used internally)
223- function resolveReferences ( obj , level , startDepth = 0 ) {
224+ // visited: Set of already visited URNs to prevent circular reference infinite loops
225+ function resolveReferences ( obj , level , startDepth = 0 , visited = new Set ( ) ) {
224226 if ( ! level || level === 'none' || ! obj || typeof obj !== 'object' ) return obj ;
225227
228+ // Prevent infinite recursion by tracking visited URNs
229+ const objUrn = obj . urn ;
230+ if ( objUrn && visited . has ( objUrn ) ) {
231+ // Already visited this object - return reference instead of full object
232+ return {
233+ urn : obj . urn ,
234+ id : obj . id ,
235+ agencyID : obj . agencyID ,
236+ version : obj . version ,
237+ typeOfObject : obj . typeOfObject ,
238+ _circularRef : true
239+ } ;
240+ }
241+
242+ // Add current URN to visited set (only for recursive 'all' level)
243+ if ( level === 'all' && objUrn ) {
244+ visited = new Set ( visited ) ;
245+ visited . add ( objUrn ) ;
246+ }
247+
226248 const resolved = JSON . parse ( JSON . stringify ( obj ) ) ; // Deep clone
227249 const isRecursive = level === 'all' ;
228250
@@ -236,12 +258,12 @@ function resolveReferences(obj, level, startDepth = 0) {
236258 }
237259
238260 // Recursively process object properties
239- function processObject ( objToProcess , depth = 0 ) {
261+ function processObject ( objToProcess , depth = 0 , visitedSet = visited ) {
240262 if ( ! objToProcess || typeof objToProcess !== 'object' ) return objToProcess ;
241263
242264 // Special handling for arrays
243265 if ( Array . isArray ( objToProcess ) ) {
244- return objToProcess . map ( item => processObject ( item , depth ) ) ;
266+ return objToProcess . map ( item => processObject ( item , depth , visitedSet ) ) ;
245267 }
246268
247269 const processed = { } ;
@@ -258,7 +280,7 @@ function resolveReferences(obj, level, startDepth = 0) {
258280 // For 'children' level: resolve only at depth 0
259281 // For 'all' level: resolve at all depths (truly recursive)
260282 if ( depth === 0 || isRecursive ) {
261- const resolved = resolveSingleReference ( value , level , isRecursive , depth ) ;
283+ const resolved = resolveSingleReference ( value , level , isRecursive , depth , visited ) ;
262284 // Replace xxxReference with xxx when resolved
263285 const resolvedKey = getResolvedPropertyName ( key ) ;
264286 processed [ resolvedKey ] = resolved ;
@@ -271,7 +293,7 @@ function resolveReferences(obj, level, startDepth = 0) {
271293 else if ( key === 'representation' && value ?. codeRepresentation ?. codeListReference ) {
272294 const codeListRef = value . codeRepresentation . codeListReference ;
273295 if ( depth === 0 || isRecursive ) {
274- const resolved = resolveSingleReference ( codeListRef , level , isRecursive , depth ) ;
296+ const resolved = resolveSingleReference ( codeListRef , level , isRecursive , depth , visited ) ;
275297 // Create a new codeRepresentation object without codeListReference
276298 const { codeListReference, ...codeRepresentationWithoutRef } = value . codeRepresentation ;
277299 processed [ key ] = {
@@ -303,7 +325,7 @@ function resolveReferences(obj, level, startDepth = 0) {
303325 const categories = loadMock ( 'categories.json' ) ;
304326 const category = findById ( categories , categoryId ) ;
305327 if ( category ) {
306- const resolvedCategory = isRecursive ? resolveReferences ( category , level , depth + 1 ) : category ;
328+ const resolvedCategory = isRecursive ? resolveReferences ( category , level , depth + 1 , visited ) : category ;
307329 // Exclude categoryReference when resolving to category
308330 const { categoryReference, ...codeWithoutRef } = code ;
309331 return {
@@ -313,7 +335,7 @@ function resolveReferences(obj, level, startDepth = 0) {
313335 }
314336 }
315337 }
316- return isRecursive ? processObject ( code , depth + 1 ) : code ;
338+ return isRecursive ? processObject ( code , depth + 1 , visited ) : code ;
317339 } ) ;
318340 }
319341 // Special handling for scheme children (concepts, variables, codeLists, categories)
@@ -333,7 +355,7 @@ function resolveReferences(obj, level, startDepth = 0) {
333355 // For 'children' level: resolve object but not its internal references
334356 // For 'all' level: resolve recursively with all references
335357 if ( isRecursive ) {
336- return resolveReferences ( child , level , depth + 1 ) ;
358+ return resolveReferences ( child , level , depth + 1 , visited ) ;
337359 } else {
338360 // For 'children' level, return the full object but don't resolve its internal references
339361 return child ;
@@ -342,18 +364,18 @@ function resolveReferences(obj, level, startDepth = 0) {
342364 }
343365 }
344366 // If not an identifier or not found, process as normal
345- return processObject ( identifier , depth + 1 ) ;
367+ return processObject ( identifier , depth + 1 , visited ) ;
346368 } ) ;
347369 } else {
348- processed [ key ] = value . map ( item => processObject ( item , depth + 1 ) ) ;
370+ processed [ key ] = value . map ( item => processObject ( item , depth + 1 , visited ) ) ;
349371 }
350372 } else {
351- processed [ key ] = value . map ( item => processObject ( item , depth + 1 ) ) ;
373+ processed [ key ] = value . map ( item => processObject ( item , depth + 1 , visited ) ) ;
352374 }
353375 }
354376 // Recursively process nested objects
355377 else if ( value && typeof value === 'object' ) {
356- processed [ key ] = processObject ( value , depth + 1 ) ;
378+ processed [ key ] = processObject ( value , depth + 1 , visited ) ;
357379 }
358380 // Keep primitive values as-is
359381 else {
0 commit comments