diff --git a/packages/core/src/v3/utils/flattenAttributes.ts b/packages/core/src/v3/utils/flattenAttributes.ts index 545b0184e6..c04e1fd093 100644 --- a/packages/core/src/v3/utils/flattenAttributes.ts +++ b/packages/core/src/v3/utils/flattenAttributes.ts @@ -3,6 +3,17 @@ import { Attributes } from "@opentelemetry/api"; export const NULL_SENTINEL = "$@null(("; export const CIRCULAR_REFERENCE_SENTINEL = "$@circular(("; +const DOT = "__DOT__"; + +function escapeKey(key: string) { + return key.replace(/\./g, DOT); // Replace . with __DOT__ +} + +function unescapeKey(key: string) { + let re = new RegExp(DOT, "g"); + return key.replace(re, '.'); // Replace __DOT__ back with . (dot) +} + export function flattenAttributes( obj: Record | Array | string | boolean | number | null | undefined, prefix?: string , @@ -53,7 +64,8 @@ export function flattenAttributes( for (const [key, value] of Object.entries(obj)) { - const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : key}`; + const escapedKey = escapeKey(key); + const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : escapedKey}`; if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { if (typeof value[i] === "object" && value[i] !== null) { @@ -118,10 +130,10 @@ export function unflattenAttributes( acc.push(parseInt(match[1])); } else { // Remove brackets for non-numeric array keys - acc.push(part.slice(1, -1)); + acc.push(unescapeKey(part.slice(1, -1))); } } else { - acc.push(part); + acc.push(unescapeKey(part)); } return acc; }, diff --git a/packages/core/test/flattenAttributes.test.ts b/packages/core/test/flattenAttributes.test.ts index 4b00995163..d6dff93cc0 100644 --- a/packages/core/test/flattenAttributes.test.ts +++ b/packages/core/test/flattenAttributes.test.ts @@ -179,6 +179,38 @@ describe("flattenAttributes", () => { "friends.[0]": "$@circular((", }); }); + + it("handles . (dot) in keys correctly", () => { + const obj = { + "Key 0.002mm": 31.4, + }; + const expected = { "Key 0__DOT__002mm": 31.4 }; + expect(flattenAttributes(obj)).toEqual(expected); + expect(unflattenAttributes(expected)).toEqual(obj); + + const obj2 = { + level1: { + level2: { + value: "test", + "level3.key": "value", + }, + array: [1, 2, 3], + }, + }; + const expected2 = { + "level1.level2.value": "test", + "level1.level2.level3__DOT__key": "value", + "level1.array.[0]": 1, + "level1.array.[1]": 2, + "level1.array.[2]": 3, + }; + expect(flattenAttributes(obj2)).toEqual(expected2); + + const PI = 3.14159265359; + const result = flattenAttributes(PI); + expect(result).toEqual({ "": PI }); + expect(unflattenAttributes(result)).toEqual(PI); + }); }); describe("unflattenAttributes", () => { @@ -260,4 +292,24 @@ describe("unflattenAttributes", () => { blogPosts: [{ title: "Post 1", author: "[Circular Reference]" }], }); }); + + it("handles espacaped . (dot) in keys correctly and returns original object", () => { + const flattened = { + "level1.level2.value": "test", + "level1.level2.level3__DOT__key": "value", + "level1.array.[0]": 1, + "level1.array.[1]": 2, + "level1.array.[2]": 3, + }; + const original = { + level1: { + level2: { + value: "test", + "level3.key": "value", + }, + array: [1, 2, 3], + }, + }; + expect(unflattenAttributes(flattened)).toEqual(original); + }); });