Problem
Before the optional chaining operator (?.) existed, it was sometimes troublesome to access deeply-nested properties in huge JavaScript objects when some of the intermediate properties might not be present.
const john = {
profile: {
name: { firstName: 'John', lastName: 'Doe' },
age: 20,
gender: 'Male',
},
};
const jane = {
profile: {
age: 19,
gender: 'Female',
},
};
function getFirstName(user) {
return user.profile.name.firstName;
}
Doing getFirstName(john) works but getFirstName(jane) will error because the name property doesn't exist for jane.profile.
Firstly, I encountered this code challenge at one of my interviews for a front-end position at one of the big tech companies. Once I've completed this task, I've been required to use this
get()function in a follow-up question about string template replacement. The second time I've seen this on GreatFrontEnd.
Lodash's Get method
Lodash's _.get method was created as a solution for such use cases.
Let's write our own version as a get function. The function gets the value at path of object. If the resolved value is undefined, the `defaultValue is returned in its place. The function signature is as such:
get(object, path, [defaultValue]);
object: The object to query.path: The path of the property to get. It can be a string with.as the separator between fields, or an array of path strings.defaultValue: Optional parameter. The value returned if the resolved value isundefined.
Examples
get(john, 'profile.name.firstName'); // 'John'
get(john, 'profile.gender'); // 'Male'
get(jane, 'profile.name.firstName'); // undefined
Arrays can also be accessed if numerical indices are provided.
get({ a: [{ b: { c: 3 } }] }, 'a.0.b.c'); // 3
There's no need to support syntax resembling get(object, 'a[0].b.c').
Solution
We start by checking if the object is empty. If it is, we return the defaultValue. Then we check if the path is an array. If it is, we use it as is. Otherwise, we split the path string by . and use the resulting array.
We then iterate over the pathArray and check if the result is an object and if it has the value key. If it does, we set the result to the value of that key. Otherwise, we return the defaultValue.
export default function get(object, path, defaultValue) {
if (!Object.keys(object).length) return defaultValue;
const pathArray = Array.isArray(path) ? path : path.split('.');
let result = object;
for (let i = 0; i < pathArray.length; i++) {
const value = pathArray[i];
if (typeof result === 'object' && result !== null && value in result) {
result = result[value];
} else {
return defaultValue;
}
}
return result;
}