-
Notifications
You must be signed in to change notification settings - Fork 156
fix(idempotency): deep sort payload during hashing #2570
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
Merged
dreamorosi
merged 9 commits into
aws-powertools:main
from
arnabrahman:2120-sort-payload-during-hashing
Jun 3, 2024
Merged
Changes from 7 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c8d2bd3
feat: sort function
arnabrahman 64b2e69
test: sort function for primitive values
arnabrahman d7d7879
test: sort function for nested JSON array/object
arnabrahman baf6e73
docs: deepSort function description
arnabrahman d6f52cf
test: remove jest beforeEach function
arnabrahman c55e2ed
feat(idempotency): deep sort the data before generating hash
arnabrahman 7b41f6a
doc: minor description change for deepSort function
arnabrahman fa88b55
chore: comment about sortObject function
arnabrahman acbeb30
Merge branch 'main' into 2120-sort-payload-during-hashing
dreamorosi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { getType } from '@aws-lambda-powertools/commons'; | ||
import { | ||
JSONArray, | ||
JSONObject, | ||
JSONValue, | ||
} from '@aws-lambda-powertools/commons/types'; | ||
|
||
const sortObject = (object: JSONObject): JSONObject => { | ||
return Object.keys(object) | ||
.sort((a, b) => (a.toLowerCase() < b.toLowerCase() ? -1 : 1)) | ||
.reduce((acc, key) => { | ||
acc[key] = deepSort(object[key]); | ||
|
||
return acc; | ||
}, {} as JSONObject); | ||
}; | ||
|
||
/** | ||
* Recursively sorts the keys of an object or elements of an array. | ||
* | ||
* This function sorts the keys of any JSON in a case-insensitive manner and recursively applies the same sorting to | ||
* nested objects and arrays. Primitives (strings, numbers, booleans, null) are returned unchanged. | ||
* | ||
* @param {JSONValue} data - The input data to be sorted, which can be an object, array or primitive value. | ||
* @returns {JSONValue} - The sorted data, with all object's keys sorted alphabetically in a case-insensitive manner. | ||
*/ | ||
|
||
arnabrahman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const deepSort = (data: JSONValue): JSONValue => { | ||
const type = getType(data); | ||
if (type === 'object') { | ||
return sortObject(data as JSONObject); | ||
} else if (type === 'array') { | ||
return (data as JSONArray).map(deepSort); | ||
} | ||
|
||
return data; | ||
}; | ||
|
||
export { deepSort }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/** | ||
* Test deepSort Function | ||
* | ||
* @group unit/idempotency/deepSort | ||
*/ | ||
import { deepSort } from '../../src/deepSort'; | ||
|
||
describe('Function: deepSort', () => { | ||
test('can sort string correctly', () => { | ||
expect(deepSort('test')).toEqual('test'); | ||
}); | ||
|
||
test('can sort number correctly', () => { | ||
expect(deepSort(5)).toEqual(5); | ||
}); | ||
|
||
test('can sort boolean correctly', () => { | ||
expect(deepSort(true)).toEqual(true); | ||
expect(deepSort(false)).toEqual(false); | ||
}); | ||
|
||
test('can sort null correctly', () => { | ||
expect(deepSort(null)).toEqual(null); | ||
}); | ||
|
||
test('can sort undefined correctly', () => { | ||
expect(deepSort(undefined)).toEqual(undefined); | ||
}); | ||
|
||
test('can sort object with nested keys correctly', () => { | ||
// Prepare | ||
const input = { | ||
name: 'John', | ||
age: 30, | ||
city: 'New York', | ||
address: { | ||
street: '5th Avenue', | ||
number: 123, | ||
}, | ||
}; | ||
|
||
// Act | ||
const result = deepSort(input); | ||
|
||
// Assess | ||
expect(JSON.stringify(result)).toEqual( | ||
JSON.stringify({ | ||
address: { | ||
number: 123, | ||
street: '5th Avenue', | ||
}, | ||
age: 30, | ||
city: 'New York', | ||
name: 'John', | ||
}) | ||
); | ||
}); | ||
|
||
test('can sort deeply nested structures', () => { | ||
// Prepare | ||
const input = { | ||
z: [{ b: { d: 4, c: 3 }, a: { f: 6, e: 5 } }], | ||
a: { c: 3, b: 2, a: 1 }, | ||
}; | ||
|
||
// Act | ||
const result = deepSort(input); | ||
|
||
//Assess | ||
expect(JSON.stringify(result)).toEqual( | ||
JSON.stringify({ | ||
a: { a: 1, b: 2, c: 3 }, | ||
z: [{ a: { e: 5, f: 6 }, b: { c: 3, d: 4 } }], | ||
}) | ||
); | ||
}); | ||
|
||
test('can sort JSON array with objects containing words as keys and nested objects/arrays correctly', () => { | ||
// Prepare | ||
const input = [ | ||
{ | ||
transactions: [ | ||
50, | ||
40, | ||
{ field: 'a', category: 'x', purpose: 's' }, | ||
[ | ||
{ | ||
zone: 'c', | ||
warehouse: 'd', | ||
attributes: { region: 'a', quality: 'x', batch: 's' }, | ||
}, | ||
], | ||
], | ||
totalAmount: 30, | ||
customerName: 'John', | ||
location: 'New York', | ||
transactionType: 'a', | ||
}, | ||
{ | ||
customerName: 'John', | ||
location: 'New York', | ||
transactionDetails: [ | ||
{ field: 'a', category: 'x', purpose: 's' }, | ||
null, | ||
50, | ||
[{ zone: 'c', warehouse: 'd', attributes: 't' }], | ||
40, | ||
], | ||
amount: 30, | ||
}, | ||
]; | ||
|
||
// Act | ||
const result = deepSort(input); | ||
|
||
// Assess | ||
expect(JSON.stringify(result)).toEqual( | ||
JSON.stringify([ | ||
{ | ||
customerName: 'John', | ||
location: 'New York', | ||
totalAmount: 30, | ||
transactions: [ | ||
50, | ||
40, | ||
{ category: 'x', field: 'a', purpose: 's' }, | ||
[ | ||
{ | ||
attributes: { batch: 's', quality: 'x', region: 'a' }, | ||
warehouse: 'd', | ||
zone: 'c', | ||
}, | ||
], | ||
], | ||
transactionType: 'a', | ||
}, | ||
{ | ||
amount: 30, | ||
customerName: 'John', | ||
location: 'New York', | ||
transactionDetails: [ | ||
{ category: 'x', field: 'a', purpose: 's' }, | ||
null, | ||
50, | ||
[{ attributes: 't', warehouse: 'd', zone: 'c' }], | ||
40, | ||
], | ||
}, | ||
]) | ||
); | ||
}); | ||
|
||
test('handles empty objects and arrays correctly', () => { | ||
expect(deepSort({})).toEqual({}); | ||
expect(deepSort([])).toEqual([]); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.