diff --git a/package-lock.json b/package-lock.json index 9f4f1a0..34992ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "doc-path", - "version": "4.0.2", + "version": "4.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "doc-path", - "version": "4.0.2", + "version": "4.1.0", "license": "MIT", "devDependencies": { "@types/mocha": "10.0.1", diff --git a/package.json b/package.json index 1dbdc61..b721e85 100755 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "mrodrig", "name": "doc-path", "description": "A document path library for Node", - "version": "4.0.2", + "version": "4.1.0", "homepage": "https://mrodrig.github.io/doc-path", "repository": { "type": "git", diff --git a/src/path.ts b/src/path.ts index 1aa6f3d..020726e 100644 --- a/src/path.ts +++ b/src/path.ts @@ -18,15 +18,22 @@ export function evaluatePath(obj: unknown, kp: string): unknown { const kpVal = typeof obj === 'object' && kp in obj ? (obj as Record)[kp] : undefined; const keyVal = typeof obj === 'object' && key in obj ? (obj as Record)[key] : undefined; - // If there is a '.' in the key path and the key path doesn't appear in the object, recur on the subobject if (dotIndex >= 0 && typeof obj === 'object' && !(kp in obj)) { + const { key: nextKey } = state(remaining); + const nextKeyAsInt = parseInt(nextKey); + // If there's an array at the current key in the object, then iterate over those items evaluating the remaining path - if (Array.isArray(keyVal)) { + if (Array.isArray(keyVal) && isNaN(nextKeyAsInt)) { return keyVal.map((doc: unknown) => evaluatePath(doc, remaining)); } // Otherwise, we can just recur return evaluatePath(keyVal, remaining); } else if (Array.isArray(obj)) { + const keyAsInt = parseInt(key); + if (kp === key && dotIndex === -1 && !isNaN(keyAsInt)) { + return keyVal; + } + // If this object is actually an array, then iterate over those items evaluating the path return obj.map((doc) => evaluatePath(doc, kp)); } else if (dotIndex >= 0 && kp !== key && typeof obj === 'object' && key in obj) { @@ -65,17 +72,44 @@ function _sp(obj: T, kp: string, v: unknown): T { } if (dotIndex >= 0) { + const keyAsInt = parseInt(key); + // If there is a '.' in the key path, recur on the subdoc and ... - if (typeof obj === 'object' && obj !== null && !(key in obj) && Array.isArray(obj)) { + if (typeof obj === 'object' && obj !== null && !(key in obj) && Array.isArray(obj) && !isNaN(keyAsInt)) { + + // If there's no value at obj[key] then populate an empty object + (obj as Record)[key] = (obj as Record)[key] ?? {}; + + // Continue iterating on the rest of the key path to set the appropriate value where intended and then return + _sp((obj as Record)[key], remaining, v); + return obj; + } else if (typeof obj === 'object' && obj !== null && !(key in obj) && Array.isArray(obj)) { // If this is an array and there are multiple levels of keys to iterate over, recur. obj.forEach((doc) => _sp(doc, kp, v)); return obj; } else if (typeof obj === 'object' && obj !== null && !(key in obj) && !Array.isArray(obj)) { - // If the current key doesn't exist yet, populate it - (obj as Record)[key] = {}; + + const { key: nextKey } = state(remaining); + const nextKeyAsInt = parseInt(nextKey); + + if (!isNaN(nextKeyAsInt)) { + // If the current key doesn't exist yet and the next key is a number (likely array index), populate an empty array + (obj as Record)[key] = []; + } else { + // If the current key doesn't exist yet, populate it + (obj as Record)[key] = {}; + } } _sp((obj as Record)[key], remaining, v); } else if (Array.isArray(obj)) { + const keyAsInt = parseInt(key); + + // If the object is an array and this key is an int (likely array index), then set the value directly and return + if (kp === key && dotIndex === -1 && !isNaN(keyAsInt)) { + (obj as Record)[key] = v; + return obj; + } + // If this "obj" is actually an array, then we can loop over each of the values and set the path obj.forEach((doc) => _sp(doc, remaining, v)); return obj; diff --git a/test/index.ts b/test/index.ts index c6e95bb..9509431 100644 --- a/test/index.ts +++ b/test/index.ts @@ -174,6 +174,52 @@ describe('doc-path Module', () => { done(); }); + + it('should evaluate the properties within an array properly', (done) => { + doc = { + list: [{ + a: 1 + }, { + a: 2 + }] + }; + + assert.deepEqual(evaluatePath(doc, 'list.a'), [1, 2]); + + done(); + }); + + it('should evaluate the property even when an array index is included in the path', (done) => { + doc = { + list: [{ + a: 1 + }, { + a: 2 + }] + }; + + assert.equal(evaluatePath(doc, 'list.0.a'), 1); + assert.equal(evaluatePath(doc, 'list.1.a'), 2); + assert.equal(evaluatePath(doc, 'list.2.a'), undefined); + + + done(); + }); + + it('should evaluate the property even when an array index is the last key in the path', (done) => { + doc = { + list: [{ + a: 1 + }, { + a: 2 + }] + }; + + assert.deepEqual(evaluatePath(doc, 'list.0'), { a: 1 }); + assert.deepEqual(evaluatePath(doc, 'list.2'), undefined); + + done(); + }); }); describe('setPath', () => { @@ -400,5 +446,27 @@ describe('doc-path Module', () => { assert.equal(doc.data.options.name, 'MacBook Pro 15'); done(); }); + + it('should set a value properly when an array index is specified in the key path', (done) => { + setPath(doc, 'list.0.a', '1'); + setPath(doc, 'list.1.a', '2'); + assert.deepEqual(doc, {list: [ {a: 1}, {a: 2} ]}); + done(); + }); + + it('should set a value properly when an array index is specified in the key path', (done) => { + setPath(doc, 'list.test.0.a', '1'); + setPath(doc, 'list.test.1', '2'); + assert.deepEqual(doc, {list: {test: [ {a: 1}, 2 ]}}); + done(); + }); + + it('should set a value properly when an array index is specified in the key path - beyond first index', (done) => { + setPath(doc, 'list.1.a', '2'); + const expected = { list: [] }; + (expected.list[1] as unknown) = {a: 2}; + assert.deepEqual(doc, expected); + done(); + }); }); });