- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 873
 
Fix: Handle circular references in flattenAttributes function #1433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
6915d9e
              f508bf5
              b29ba40
              e58686c
              400dbb2
              c908f3a
              cb3f598
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -1,10 +1,12 @@ | ||
| import { Attributes } from "@opentelemetry/api"; | ||
| 
     | 
||
| export const NULL_SENTINEL = "$@null(("; | ||
| export const CIRCULAR_REFERENCE_SENTINEL = "$@circular(("; | ||
| 
     | 
||
| export function flattenAttributes( | ||
| obj: Record<string, unknown> | Array<unknown> | string | boolean | number | null | undefined, | ||
| prefix?: string | ||
| prefix?: string , | ||
| seen: WeakSet<object> = new WeakSet() | ||
| ): Attributes { | ||
| const result: Attributes = {}; | ||
| 
     | 
||
| 
          
            
          
           | 
    @@ -38,13 +40,25 @@ export function flattenAttributes( | |
| return result; | ||
| } | ||
| 
     | 
||
| // Check for circular reference | ||
| if (typeof obj === "object" && seen.has(obj)) { | ||
                
      
                  ericallam marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| result[prefix || ""] = CIRCULAR_REFERENCE_SENTINEL; | ||
| return result; | ||
| } | ||
| 
     | 
||
| // Add object to seen set | ||
| if (typeof obj === "object") { | ||
                
      
                  ericallam marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| seen.add(obj); | ||
| } | ||
| 
     | 
||
| 
     | 
||
| for (const [key, value] of Object.entries(obj)) { | ||
| const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : key}`; | ||
| if (Array.isArray(value)) { | ||
| for (let i = 0; i < value.length; i++) { | ||
| if (typeof value[i] === "object" && value[i] !== null) { | ||
| // update null check here as well | ||
| Object.assign(result, flattenAttributes(value[i], `${newPrefix}.[${i}]`)); | ||
| Object.assign(result, flattenAttributes(value[i], `${newPrefix}.[${i}]`,seen)); | ||
| } else { | ||
| if (value[i] === null) { | ||
| result[`${newPrefix}.[${i}]`] = NULL_SENTINEL; | ||
| 
        
          
        
         | 
    @@ -55,7 +69,7 @@ export function flattenAttributes( | |
| } | ||
| } else if (isRecord(value)) { | ||
| // update null check here | ||
| Object.assign(result, flattenAttributes(value, newPrefix)); | ||
| Object.assign(result, flattenAttributes(value, newPrefix, seen)); | ||
| } else { | ||
| if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") { | ||
| result[newPrefix] = value; | ||
| 
          
            
          
           | 
    @@ -135,8 +149,10 @@ export function unflattenAttributes( | |
| } | ||
| 
     | 
||
| const lastPart = parts[parts.length - 1]; | ||
| 
     | 
||
| if (lastPart !== undefined) { | ||
| current[lastPart] = rehydrateNull(value); | ||
| current[lastPart] = rehydrateNull(rehydrateCircular(value)); | ||
| 
     | 
||
| } | ||
| } | ||
| 
     | 
||
| 
        
          
        
         | 
    @@ -153,6 +169,13 @@ export function unflattenAttributes( | |
| return result; | ||
| } | ||
| 
     | 
||
| function rehydrateCircular(value: any): any { | ||
| if (value === CIRCULAR_REFERENCE_SENTINEL) { | ||
| return "[Circular Reference]"; | ||
| } | ||
| return value; | ||
| } | ||
| 
         
      Comment on lines
    
      +172
     to 
      +177
    
   
  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider parameterizing the circular reference placeholder. The hardcoded string  -function rehydrateCircular(value: any): any {
+function rehydrateCircular(
+  value: any,
+  placeholder: string = "[Circular Reference]"
+): any {
   if (value === CIRCULAR_REFERENCE_SENTINEL) {
-    return "[Circular Reference]";
+    return placeholder;
   }
   return value;
 }Then update the call site: -current[lastPart] = rehydrateNull(rehydrateCircular(value));
+current[lastPart] = rehydrateNull(rehydrateCircular(value, options?.circularPlaceholder));
  | 
||
| 
     | 
||
| export function primitiveValueOrflattenedAttributes( | ||
| obj: Record<string, unknown> | Array<unknown> | string | boolean | number | undefined, | ||
| prefix: string | undefined | ||
| 
          
            
          
           | 
    ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure the uniqueness of
CIRCULAR_REFERENCE_SENTINELto prevent data conflicts.While
CIRCULAR_REFERENCE_SENTINELis used to indicate circular references, there's a possibility that user data may contain this exact string, leading to incorrect interpretation during unflattening. Consider using a unique symbol or a less common string to minimize the risk of conflicts with actual data values.