From 370899889b4bbf046aa765f25f428377eae36819 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Tue, 5 Nov 2024 09:28:58 +0100 Subject: [PATCH] fix: performance issue of sorting the keys of large objects --- src/lib/logic/sort.test.ts | 35 +++++++++-------------------------- src/lib/logic/sort.ts | 26 ++++++++++++-------------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/lib/logic/sort.test.ts b/src/lib/logic/sort.test.ts index 7a1d74c2..a2b2bbe1 100644 --- a/src/lib/logic/sort.test.ts +++ b/src/lib/logic/sort.test.ts @@ -17,9 +17,7 @@ describe('sort', () => { const object = { b: 1, c: 1, a: 1 } assert.deepStrictEqual(sortJson(object, undefined, undefined, 1), [ - { op: 'move', from: '/a', path: '/a' }, - { op: 'move', from: '/b', path: '/b' }, - { op: 'move', from: '/c', path: '/c' } + { op: 'replace', path: '', value: { a: 1, b: 1, c: 1 } } ]) }) @@ -27,9 +25,7 @@ describe('sort', () => { const object = { b: 1, c: 1, a: 1 } assert.deepStrictEqual(sortJson(object, undefined, undefined, -1), [ - { op: 'move', from: '/c', path: '/c' }, - { op: 'move', from: '/b', path: '/b' }, - { op: 'move', from: '/a', path: '/a' } + { op: 'replace', path: '', value: { c: 1, b: 1, a: 1 } } ]) }) @@ -41,9 +37,7 @@ describe('sort', () => { } assert.deepStrictEqual(sortJson(object, ['root', 'path']), [ - { op: 'move', from: '/root/path/a', path: '/root/path/a' }, - { op: 'move', from: '/root/path/b', path: '/root/path/b' }, - { op: 'move', from: '/root/path/c', path: '/root/path/c' } + { op: 'replace', path: '/root/path', value: { a: 1, b: 1, c: 1 } } ]) }) @@ -51,9 +45,7 @@ describe('sort', () => { const object = [{ b: 1, c: 1, a: 1 }] assert.deepStrictEqual(sortJson(object, ['0']), [ - { op: 'move', from: '/0/a', path: '/0/a' }, - { op: 'move', from: '/0/b', path: '/0/b' }, - { op: 'move', from: '/0/c', path: '/0/c' } + { op: 'replace', path: '/0', value: { a: 1, b: 1, c: 1 } } ]) }) @@ -97,21 +89,15 @@ describe('sort', () => { const object = { b: 1, c: 1, a: 1 } assert.deepStrictEqual(sortObjectKeys(object), [ - { op: 'move', from: '/a', path: '/a' }, - { op: 'move', from: '/b', path: '/b' }, - { op: 'move', from: '/c', path: '/c' } + { op: 'replace', path: '', value: { a: 1, b: 1, c: 1 } } ]) assert.deepStrictEqual(sortObjectKeys(object, undefined, 1), [ - { op: 'move', from: '/a', path: '/a' }, - { op: 'move', from: '/b', path: '/b' }, - { op: 'move', from: '/c', path: '/c' } + { op: 'replace', path: '', value: { a: 1, b: 1, c: 1 } } ]) assert.deepStrictEqual(sortObjectKeys(object, undefined, -1), [ - { op: 'move', from: '/c', path: '/c' }, - { op: 'move', from: '/b', path: '/b' }, - { op: 'move', from: '/a', path: '/a' } + { op: 'replace', path: '', value: { c: 1, b: 1, a: 1 } } ]) }) @@ -123,9 +109,7 @@ describe('sort', () => { } assert.deepStrictEqual(sortObjectKeys(object, ['root', 'path']), [ - { op: 'move', from: '/root/path/a', path: '/root/path/a' }, - { op: 'move', from: '/root/path/b', path: '/root/path/b' }, - { op: 'move', from: '/root/path/c', path: '/root/path/c' } + { op: 'replace', path: '/root/path', value: { a: 1, b: 1, c: 1 } } ]) }) @@ -133,8 +117,7 @@ describe('sort', () => { const object = { B: 1, a: 1 } assert.deepStrictEqual(sortObjectKeys(object), [ - { op: 'move', from: '/a', path: '/a' }, - { op: 'move', from: '/B', path: '/B' } + { op: 'replace', path: '', value: { a: 1, B: 1 } } ]) }) diff --git a/src/lib/logic/sort.ts b/src/lib/logic/sort.ts index 0cf72044..7e635f92 100644 --- a/src/lib/logic/sort.ts +++ b/src/lib/logic/sort.ts @@ -61,7 +61,7 @@ export function sortJson( * object to be sorted * @param [rootPath=[]] Relative path when the array was located * @param [direction=1] Pass 1 to sort ascending, -1 to sort descending - * @return Returns a JSONPatch document with move operation + * @return Returns a JSONPatch document with operations * to get the array sorted. */ export function sortObjectKeys( @@ -69,7 +69,7 @@ export function sortObjectKeys( rootPath: JSONPath = [], direction: 1 | -1 = 1 ): JSONPatchDocument { - const object = getIn(json, rootPath) + const object = getIn(json, rootPath) as Record const keys = Object.keys(object as unknown as Record) const sortedKeys = keys.slice() @@ -77,19 +77,17 @@ export function sortObjectKeys( return direction * caseInsensitiveNaturalCompare(keyA, keyB) }) - // TODO: can we make this more efficient? check if the first couple of keys are already in order and if so ignore them - const operations: JSONPatchDocument = [] - for (let i = 0; i < sortedKeys.length; i++) { - const key = sortedKeys[i] - const path = compileJSONPointer(rootPath.concat(key)) - operations.push({ - op: 'move', - from: path, - path - }) - } + // for performance reasons, do a full replace (we could also create a move operation for every key) + const sortedObject: Record = {} + sortedKeys.forEach((key) => (sortedObject[key] = object[key])) - return operations + return [ + { + op: 'replace', + path: compileJSONPointer(rootPath), + value: sortedObject + } + ] } /**