Skip to content

Commit

Permalink
fix(commons): improve maps and sets handling by serialize
Browse files Browse the repository at this point in the history
  • Loading branch information
delatrie committed Sep 3, 2024
1 parent 35f3fc4 commit 29c1f69
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 12 deletions.
34 changes: 22 additions & 12 deletions packages/allure-js-commons/src/sdk/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,34 +144,44 @@ export const isPromise = <T = any>(obj: any): obj is PromiseLike<T> =>

export const serialize = (value: any, { maxDepth = 0, maxLength = 0 }: SerializeOptions = {}): string =>
limitString(
typeof value === "object"
? JSON.stringify(
value instanceof Map || value instanceof Set ? Array.from(value) : value,
createSerializeReplacer(maxDepth),
)
: String(value),
typeof value === "object" ? JSON.stringify(value, createSerializeReplacer(maxDepth)) : String(value),
maxLength,
);

const createSerializeReplacer = (maxDepth: number) => {
const parents: any[] = [];
return function (this: any, _: string, v: unknown) {
if (typeof v !== "object" || v === null) {
return v;
return function (this: any, _: string, value: unknown) {
if (typeof value !== "object" || value === null) {
return value;
}

while (parents.length > 0 && !Object.is(parents.at(-1), this)) {
parents.pop();
}

if ((maxDepth && parents.length >= maxDepth) || parents.includes(v)) {
if ((maxDepth && parents.length >= maxDepth) || parents.includes(value)) {
return undefined;
}

parents.push(v);
return v;
parents.push(value);

return value instanceof Map
? excludeCircularRefsFromMap(parents, value)
: value instanceof Set
? excludeCircularRefsFromSet(parents, value)
: value;
};
};

const excludeCircularRefsFromMap = (parents: any[], map: Map<any, any>) => {
return Array.from(map)
.filter(([k]) => !parents.includes(k))
.map(([k, v]) => [k, parents.includes(v) ? undefined : v]);
};

const excludeCircularRefsFromSet = (parents: any[], set: Set<any>) => {
return Array.from(set).map((v) => (parents.includes(v) ? undefined : v));
};

const limitString = (value: string, maxLength: number) =>
maxLength && value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
28 changes: 28 additions & 0 deletions packages/allure-js-commons/test/sdk/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,14 @@ describe("serialize", () => {
const obj6: any = [1, "Lorem", ["Ipsum"]];
obj6[2].push(obj6);
expect(serialize(obj6)).toBe(JSON.stringify([1, "Lorem", ["Ipsum", null]]));

const obj7: Map<number, any> = new Map();
obj7.set(1, obj7);
expect(serialize(obj7)).toBe(JSON.stringify([[1, null]]));

const obj8: Set<any> = new Set();
obj8.add(obj8);
expect(serialize(obj8)).toBe(JSON.stringify([null]));
});

it("should limit the maximum size of the serialized object", () => {
Expand All @@ -375,6 +383,26 @@ describe("serialize", () => {
);
});

it("should replace nested maps and sets with arrays", () => {
expect(
serialize({
foo: new Map([
[1, "a"],
[2, "b"],
]),
bar: new Set([1, 2]),
}),
).toBe(
JSON.stringify({
foo: [
[1, "a"],
[2, "b"],
],
bar: [1, 2],
}),
);
});

describe("of type Array", () => {
it("should return the same serialized array as JSON.stringify", () => {
expect(serialize([])).toBe(JSON.stringify([]));
Expand Down

0 comments on commit 29c1f69

Please sign in to comment.