diff --git a/dist/path.js b/dist/path.js index d0136fc..c594e89 100644 --- a/dist/path.js +++ b/dist/path.js @@ -3,4 +3,4 @@ * doc-path * Copyright (c) 2015-present, Michael Rodrigues. */ -"use strict";function evaluatePath(t,r){if(!t)return null;let{dotIndex:e,key:a,remaining:i}=state(r);return e>=0&&!t[r]?Array.isArray(t[a])?t[a].map(t=>evaluatePath(t,i)):evaluatePath(t[a],i):Array.isArray(t)?t.map(t=>evaluatePath(t,r)):t[r]}function setPath(t,r,e){if(!t)throw new Error("No object was provided.");if(!r)throw new Error("No keyPath was provided.");return _sp(t,r,e)}function _sp(t,r,e){let{dotIndex:a,key:i,remaining:n}=state(r);if(r.startsWith("__proto__")||r.startsWith("constructor")||r.startsWith("prototype"))return t;if(a>=0){if(!t[i]&&Array.isArray(t))return t.forEach(t=>_sp(t,r,e));t[i]||(t[i]={}),_sp(t[i],n,e)}else{if(Array.isArray(t))return t.forEach(t=>_sp(t,n,e));t[r]=e}return t}function state(t){let r=t.indexOf(".");return{dotIndex:r,key:t.slice(0,r>=0?r:void 0),remaining:t.slice(r+1)}}module.exports={evaluatePath:evaluatePath,setPath:setPath}; \ No newline at end of file +"use strict";function evaluatePath(t,e){if(!t)return null;let{dotIndex:r,key:a,remaining:i}=state(e);return r>=0&&!t[e]?Array.isArray(t[a])?t[a].map(t=>evaluatePath(t,i)):evaluatePath(t[a],i):Array.isArray(t)?t.map(t=>evaluatePath(t,e)):r>=0&&e!==a&&t[a]?evaluatePath(t[a],i):-1===r&&t[a]&&!t[e]?t[a]:t[e]}function setPath(t,e,r){if(!t)throw new Error("No object was provided.");if(!e)throw new Error("No keyPath was provided.");return _sp(t,e,r)}function isObject(t){return"object"==typeof t&&null!==t}function _sp(t,e,r){let{dotIndex:a,key:i,remaining:n}=state(e);if(e.startsWith("__proto__")||e.startsWith("constructor")||e.startsWith("prototype"))return t;if(a>=0){if(!t[i]&&Array.isArray(t))return t.forEach(t=>_sp(t,e,r));if(t[i]){if(!isObject(t[i]))return t[e]=r,t}else t[i]={};_sp(t[i],n,r)}else{if(Array.isArray(t))return t.forEach(t=>_sp(t,n,r));t[i]=r}return t}function state(t){let e=/(?=0?r:void 0).replace(/\\./g,"."),remaining:t.slice(r+1)}}module.exports={evaluatePath:evaluatePath,setPath:setPath}; \ No newline at end of file diff --git a/lib/path.js b/lib/path.js index 97743ac..1d11ec0 100644 --- a/lib/path.js +++ b/lib/path.js @@ -28,6 +28,12 @@ function evaluatePath(obj, kp) { } else if (Array.isArray(obj)) { // 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 && obj[key]) { + // If there's a field with a non-nested dot, then recur into that sub-value + return evaluatePath(obj[key], remaining); + } else if (dotIndex === -1 && obj[key] && !obj[kp]) { + // If the field is here, but the key was escaped + return obj[key]; } // Otherwise, we can just return value directly @@ -51,6 +57,10 @@ function setPath(obj, kp, v) { return _sp(obj, kp, v); } +function isObject(v) { + return typeof v === 'object' && v !== null; +} + /** * Helper function that will set the value in the provided object/array. * @param obj {Object|Array} object to set value in @@ -75,6 +85,9 @@ function _sp(obj, kp, v) { } else if (!obj[key]) { // If the current key doesn't exist yet, populate it obj[key] = {}; + } else if (!isObject(obj[key])) { + obj[kp] = v; + return obj; } _sp(obj[key], remaining, v); } else if (Array.isArray(obj)) { @@ -82,7 +95,7 @@ function _sp(obj, kp, v) { return obj.forEach((doc) => _sp(doc, remaining, v)); } else { // Otherwise, we can set the path directly - obj[kp] = v; + obj[key] = v; } return obj; @@ -95,11 +108,12 @@ function _sp(obj, kp, v) { * @returns {{dotIndex: Number, key: String, remaining: String}} */ function state(kp) { - let dotIndex = kp.indexOf('.'); + let match = (/(?= 0 ? dotIndex : undefined), + key: kp.slice(0, dotIndex >= 0 ? dotIndex : undefined).replace(/\\./g, '.'), remaining: kp.slice(dotIndex + 1) }; } diff --git a/test/tests.js b/test/tests.js index 0db9297..a37d3bc 100644 --- a/test/tests.js +++ b/test/tests.js @@ -62,7 +62,7 @@ describe('doc-path Module', function() { }, 'testProperty.testProperty2': 'testVal2' }; - let returnVal = path.evaluatePath(doc, 'testProperty.testProperty2'); + let returnVal = path.evaluatePath(doc, 'testProperty\\.testProperty2'); assert.equal(returnVal, 'testVal2'); done(); }); @@ -111,6 +111,32 @@ describe('doc-path Module', function() { returnVal.should.deepEqual(['A/C', 'Radio']); done(); }); + + it('should work with nested dots in the path when escaped properly', (done) => { + doc = { + 'a.a': 'a', + 'a.b': { + 'c.d': '4', + c: '5', + 'c.f': '6' + }, + a: { + a: 1, + b: 2, + 'b.c.d': 32 + } + }; + // Normal paths: + path.evaluatePath(doc, 'a.a').should.equal(1); + path.evaluatePath(doc, 'a.b').should.equal(2); + // Nested dot paths: + path.evaluatePath(doc, 'a\\.a').should.equal('a'); + path.evaluatePath(doc, 'a\\.b.c\\.d').should.equal('4'); + path.evaluatePath(doc, 'a\\.b.c').should.equal('5'); + path.evaluatePath(doc, 'a\\.b.c\\.f').should.equal('6'); + path.evaluatePath(doc, 'a.b\\.c\\.d').should.equal(32); + done(); + }); }); describe('setPath', () => { @@ -279,5 +305,48 @@ describe('doc-path Module', function() { assert.equal(Object.polluted, undefined); done(); }); + + it('should be able to set paths with nested dots correctly', (done) => { + doc = { + 'a.a': 'a', + 'a.b': { + 'c.d': '4', + c: '5', + 'c.f': '6' + }, + a: { + a: 1, + b: 2, + 'b.c.d': 32 + } + }; + // Normal paths: + path.setPath(doc, 'a.a', 'b'); + doc.a.a.should.equal('b'); + doc['a.a'].should.not.equal('b'); + + path.setPath(doc, 'a.b', 3); + doc.a.b.should.equal(3); + doc['a.b'].should.not.equal(3); + + // Nested dot paths: + path.setPath(doc, 'a\\.a', 1); + doc['a.a'].should.equal(1); + doc.a.a.should.not.equal(1); + + path.setPath(doc, 'a\\.b.c\\.d', 4); + doc['a.b']['c.d'].should.equal(4); + + path.setPath(doc, 'a\\.b.c', 5); + doc['a.b'].c.should.equal(5); + + path.setPath(doc, 'a\\.b.c\\.f', 6); + doc['a.b']['c.f'].should.equal(6); + + path.setPath(doc, 'a.b\\.c\\.d', 32); + doc.a['b.c.d'].should.equal(32); + + done(); + }); }); });