Skip to content
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

fix(lib-dynamodb): skip function properties in data objects for DynamoDB #5697

Merged
merged 7 commits into from
Jan 31, 2024
9 changes: 9 additions & 0 deletions lib/lib-dynamodb/src/commands/marshallInput.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ describe("marshallInput and processObj", () => {
}
);
});

it("marshallInput should ignore function properties", () => {
const input = { Items: [() => {}, 1, "test"] };
const inputKeyNodes = { Items: null };
const output = { Items: { L: [{ N: "1" }, { S: "test" }] } };
expect(
marshallInput(input, inputKeyNodes, { convertTopLevelContainer: true, convertClassInstanceToMap: true })
).toEqual(output);
});
});

describe("marshallInput for commands", () => {
Expand Down
91 changes: 91 additions & 0 deletions lib/lib-dynamodb/src/commands/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,94 @@ describe("utils", () => {
});
});
});

describe("object with function property", () => {
const notAttrValue = { NotAttrValue: "NotAttrValue" };
const keyNodes = { Item: {} };
const nativeAttrObj = { Item: { id: 1, func: () => {} }, ...notAttrValue };
const attrObj = { Item: { id: { N: "1" } }, ...notAttrValue };
it("should remove functions", () => {
expect(
marshallInput(nativeAttrObj, keyNodes, { convertTopLevelContainer: true, convertClassInstanceToMap: true })
).toEqual(attrObj);
});

// List of functions
const listOfFunctions = { Item: { id: 1, funcs: [() => {}, () => {}] }, ...notAttrValue };
it("should remove functions from lists", () => {
expect(
marshallInput(listOfFunctions, keyNodes, { convertTopLevelContainer: true, convertClassInstanceToMap: true })
).toEqual({ Item: { id: { N: "1" }, funcs: { L: [] } }, ...notAttrValue });
});

// Nested list of functions
const nestedListOfFunctions = {
Item: {
id: 1,
funcs: [
[() => {}, () => {}],
[() => {}, () => {}],
],
},
...notAttrValue,
};
it("should remove functions from nested lists", () => {
expect(
marshallInput(nestedListOfFunctions, keyNodes, {
convertTopLevelContainer: true,
convertClassInstanceToMap: true,
})
).toEqual({ Item: { id: { N: "1" }, funcs: { L: [{ L: [] }, { L: [] }] } }, ...notAttrValue });
});

// Nested list of functions 3 levels down
const nestedListOfFunctions3Levels = {
Item: {
id: 1,
funcs: [
[
[() => {}, () => {}],
[() => {}, () => {}],
],
[
[() => {}, () => {}],
[() => {}, () => {}],
],
],
},
...notAttrValue,
};

it("should remove functions from a nested list of depth 3", () => {
expect(
marshallInput(nestedListOfFunctions3Levels, keyNodes, {
convertTopLevelContainer: true,
convertClassInstanceToMap: true,
})
).toEqual({
Item: {
id: { N: "1" },
funcs: {
L: [
{
L: [{ L: [] }, { L: [] }],
},
{
L: [{ L: [] }, { L: [] }],
},
],
},
},
...notAttrValue,
});
});
it("should throw when recursion depth has exceeded", () => {
const obj = {} as any;
obj.SELF = obj;
expect(() => marshallInput(obj, {}, { convertClassInstanceToMap: true })).toThrow(
new Error(
"Recursive copy depth exceeded 1000. Please set options.convertClassInstanceToMap to false and manually remove functions from your data object."
)
);
});
});
31 changes: 30 additions & 1 deletion lib/lib-dynamodb/src/commands/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,41 @@ const processAllKeysInObj = (obj: any, processFunc: Function, keyNodes: KeyNodes
}, {} as any);
};

function copyWithoutFunctions(o: any, depth = 0): any {
siddsriv marked this conversation as resolved.
Show resolved Hide resolved
if (depth > 1000) {
throw new Error(
"Recursive copy depth exceeded 1000. Please set options.convertClassInstanceToMap to false and manually remove functions from your data object."
);
}
if (typeof o === "object" || typeof o === "function") {
if (Array.isArray(o)) {
return o.filter((item) => typeof item !== "function").map((item) => copyWithoutFunctions(item, depth + 1));
}
if (o === null) {
return null;
}
const copy = {} as any;
for (const [key, value] of Object.entries(o)) {
if (typeof value !== "function") {
copy[key] = copyWithoutFunctions(value, depth + 1);
}
}
return copy;
} else {
return o;
}
}

/**
* @internal
*/
export const marshallInput = (obj: any, keyNodes: KeyNodeChildren, options?: marshallOptions) => {
let _obj = obj;
if (options?.convertClassInstanceToMap) {
_obj = copyWithoutFunctions(obj);
}
const marshallFunc = (toMarshall: any) => marshall(toMarshall, options);
return processKeysInObj(obj, marshallFunc, keyNodes);
return processKeysInObj(_obj, marshallFunc, keyNodes);
};

/**
Expand Down
Loading