-
-
Save dfkaye/59263b51cf1e0b633181c5f44ae2066a to your computer and use it in GitHub Desktop.
// 28 June 2017 | |
// Quick-and-dirty implementation. | |
// 30 Sept 2021 | |
// Changes prompted by @nativekar: | |
// - Allow pathName param to be either Array or string. | |
// - Support "value[property]" notation. | |
// House-cleaning: | |
// - Move the inline mocha suite to own file, replace with inline test closure. | |
// - Add "_" inline definition for standalone test execution. | |
// Uncomment to set _ in test environment: | |
// var _ = globalThis._ ?? {}; | |
/** | |
* _.get() polyfill for q&d implementation. | |
* lodash has _.get(data, path, defaultValue), but underscore does not. | |
* makes data traversal so nice. | |
* see https://lodash.com/docs/4.17.4#get | |
*/ | |
typeof _.get == 'function' || (function() { | |
/** | |
* @param {object} object, The object to query. | |
* @param {Array|string} pathName: The path of the property to get. | |
* @param {*} [defaultValue]: Optional value to return for undefined resolved values. | |
* @returns {*} resolved value, or default value if specified. | |
*/ | |
var at = function get(object, pathName, defaultValue) { | |
// Coerce pathName to a string (even it turns into "[object Object]"). | |
var path = Array.isArray(pathName) | |
? pathName.join(".") | |
: String(pathName); | |
// Support bracket notation, e.g., "a[0].b.c". | |
var match = /\[\\?("|')?(\w|d)+\\?("|')?\]/g; | |
var parts = path | |
.replace(match, (m, i, v) => "." + v) | |
.split('.'); | |
var length = parts.length; | |
var i = 0; | |
// In case object isn't a real object, set it to undefined. | |
var value = object === Object(object) ? object : undefined; | |
while (value != null && i < length) { | |
value = value[parts[i++]]; | |
} | |
/** | |
* lodash.get() returns the resolved value if | |
* 1. iteration happened (i > 0) | |
* 2. iteration completed (i === length) | |
* 3. the value at the path is found in the data structure (not undefined). Note that if the path is found but the | |
* value is null, then null is returned. | |
* If any of those checks fails, return the defaultValue param, if provided. | |
*/ | |
return i && i === length && value !== undefined ? value : defaultValue; | |
}; | |
_.get = at; | |
}()); | |
/* test it out */ | |
~(function suite() { | |
// suite replaces inline mocha suite which has been moved to its own file. | |
var data = { a: { b: { c: { d: 'hello' } } } }; | |
var defaultValue = "I'm the default value"; | |
var unresolved = 'not resolved'; | |
var expected = 6; | |
var instance = Object(1); | |
var tests = [ | |
// should find value by pathName. | |
_.get(data, 'a.b.c.d') === 'hello', | |
// should return default value when pathName not resolved. | |
_.get({}, 'a.b.c.d', defaultValue) === defaultValue, | |
// should not resolve non-object data. | |
_.get('1', 'toString', unresolved) === unresolved, | |
// should not resolve with an invalid pathName. | |
_.get({ name: 'hello' }, '', unresolved) === unresolved, | |
// should resolve on arrays with valid index pathName. | |
_.get([1, 2, [3, 4, [5, expected]]], '2.2.1', expected) === expected, | |
// should resolve on coerced-to-an-object with valid pathName. | |
_.get(instance, 'toString').call(instance) === "1", | |
// should resolve pathName as an Array of descendant property names. | |
_.get(data, ['a', 'b', 'c', 'd']) === 'hello', | |
// should resolve pathName containing bracket property names. | |
_.get(data, 'a["b"]["c"]["d"]') === 'hello', | |
// should resolve pathName containing bracket property names. | |
_.get(data, ['a["b"]', 'c', 'd']) === 'hello' | |
]; | |
// Should print array with all values `true`. | |
console.log( tests ); | |
}()); |
// 30 Sept 2021 | |
// moved the inline mocha suite to own file | |
describe('_.get() method', function() { | |
/** | |
* Verification of the _.get() utility method. | |
* using mocha and chai.assert | |
*/ | |
it('should find value by path', function (done) { | |
var data = { | |
a: { | |
b: { | |
c: { | |
d: 'hello' | |
} | |
} | |
} | |
}; | |
var value = _.get(data, 'a.b.c.d'); | |
assert.equal(value, 'hello'); | |
done(); | |
}); | |
it('should return default value when path not resolved', function (done) { | |
var defaultValue = "I'm the default value"; | |
var value = _.get({}, 'a.b.c.d', defaultValue); | |
assert.equal(value, defaultValue); | |
done(); | |
}); | |
it('should not resolve non-object data', function (done) { | |
var unresolved = 'not resolved'; | |
var value = _.get('1', 'toString', unresolved); | |
assert.equal(value, unresolved); | |
done(); | |
}); | |
it('should not resolve with an invalid pathString', function (done) { | |
var unresolved = 'not resolved'; | |
var value = _.get({ name: 'hello' }, '', unresolved); | |
assert.equal(value, unresolved); | |
done(); | |
}); | |
it('should resolve on arrays with valid index pathString', function(done) { | |
var expected = 6; | |
var value = _.get([1, 2, [3, 4, [5, expected]]], '2.2.1', expected); | |
assert.equal(value, expected); | |
done(); | |
}); | |
it('should resolve on coerced-to-an-object with valid pathString', function (done) { | |
var instance = Object(1); | |
var value = _.get(instance, 'toString'); | |
// it's a function, try to call it | |
assert.strictEqual(value.call(instance), '1'); | |
assert.notStrictEqual(value.call(instance), 1); | |
// coerced toString output | |
assert.equal(value, 'function toString() { [native code] }'); | |
done(); | |
}); | |
}); | |
The 2nd argument, pathString isn't always a String. Most of the time, it can also be an array.
@nativekar I wasn’t aware of that. But then I haven’t used this in a long time. Might fix this in a couple days.
Thanks for pointing it out.
Cheers! Look forward to the fix. (I have started working on one of mine, maybe we can compare notes)
@nativekar - OK, I've updated the method to accept arrays as pathName
params, added support for value[property]
notation, and redone the inline tests as a closure (and moved the mocha suite to its own file). Let me know if you have questions.
@dfkaye - this looks perfect. Thanks for working on this.
@nativekar - Thanks. So, I looked up underscore docs to see if they had a get method now - turns out they do - https://underscorejs.org/#get - oh well…
using this, thanks!