How to get the value of an object from a specific path with vanilla JS
In response to my article on how to create a new object with only a subset of properties using vanilla JS, Twitter user zomars asked:
How about an equivalent for lodash’s
get
method?
Challenge accepted!
By the way… I love getting tweets like this. Keep them coming!
What _.get()
does
The get()
method in lodash get’s the value of an object at a specific path, and let’s you optionally return a default value if one isn’t found.
The path can be an array or a string.
var obj = {
'a': [
{
'b': {
'c': 3
}
}
]
};
// returns 3
_.get(obj, 'a[0].b.c');
// also returns 3
_.get(obj, ['a', '0', 'b', 'c']);
// returns "default"
_.get(obj, 'a.b.c', 'default');
Creating a vanilla JS get()
method
First, let’s setup a get()
helper function, and accept three arguments: the object (obj
), the path (path
), and optionally, a default value (def
).
var get = function (obj, path, def) {
// Code will go here...
};
Handling array paths
Parsing a string path
into something we can use is a bit complicated, so let’s first look at how to handle an array.
We want to loop through the array of keys/indexes. On each iteration, we’ll check to see if the key/index exists in the object. If it does, we’ll cache our current spot in the object to a variable, loop again, and look at the new cached spot for the next key/index.
If at any point the key or index doesn’t exist, we can return the default value (if one isn’t provided, it will return undefined
, which is what we want anyways).
If we complete all of the loops, we’ll return our match.
var get = function (obj, path, def) {
// Cache the current object
var current = obj;
// For each item in the path, dig into the object
for (var i = 0; i < path.length; i++) {
// If the item isn't found, return the default (or null)
if (!current[path[i]]) return def;
// Otherwise, update the current value
current = current[path[i]];
}
return current;
};
Now, we can do something like this.
var obj = {
'a': [
{
'b': {
'c': 3
}
}
]
};
var getPath = get(obj, ['a', '0', 'b', 'c']);
// Logs 3
console.log(getPath);
Now let’s look at how to handle string paths.
Handing string paths
We need to convert a string path into an array to work with it.
Let’s setup a new helper function (that will live inside get()
) called stringToPath()
. It will accept the path
as an argument.
var stringToPath = function (path) {
// Code goes here...
};
If the path
isn’t a string, we’ll return it as is. Otherwise, we’ll create a new array called output
that we’ll push stuff from the string into.
var stringToPath = function (path) {
// If the path isn't a string, return it
if (typeof path !== 'string') return path;
// Create new array
var output = [];
};
Next, we’ll use the split()
method to convert our string into an array, using a dot (.
) as the delimiter.
Each item separated by a .
is now it’s own item in an array. Sounds good, right? The one hiccup here is that the path can also contain bracket notations (for both objects and array indexes).
var stringToPath = function (path) {
// If the path isn't a string, return it
if (typeof path !== 'string') return path;
// Create new array
var output = [];
// Split to an array with dot notation
path.split('.').forEach(function (item) {
console.log(item);
});
};
The code above would log 'a[0]', 'b', 'c'
. We want that first item, a[0]
, to be two separate items.
We’re going to use split()
again—this time using a regex pattern to split on items between square brackets ([]
). If there aren’t any square brackets in the string, the whole string will be used.
Finally, we can push each item to our output
array and return it.
var stringToPath = function (path) {
// If the path isn't a string, return it
if (typeof path !== 'string') return path;
// Create new array
var output = [];
// Split to an array with dot notation
path.split('.').forEach(function (item, index) {
// Split to an array with bracket notation
item.split(/\[([^}]+)\]/g).forEach(function (key) {
// Push to the new array
if (key.length > 0) {
output.push(key);
}
});
});
return output;
};
Putting it all together
The one last thing we need to do is set our path to the output of stringToPath()
. And now we’re good to go!
var get = function (obj, path, def) {
/**
* If the path is a string, convert it to an array
* @param {String|Array} path The path
* @return {Array} The path array
*/
var stringToPath = function (path) {
// If the path isn't a string, return it
if (typeof path !== 'string') return path;
// Create new array
var output = [];
// Split to an array with dot notation
path.split('.').forEach(function (item, index) {
// Split to an array with bracket notation
item.split(/\[([^}]+)\]/g).forEach(function (key) {
// Push to the new array
if (key.length > 0) {
output.push(key);
}
});
});
return output;
};
// Get the path as an array
path = stringToPath(path);
// Cache the current object
var current = obj;
// For each item in the path, dig into the object
for (var i = 0; i < path.length; i++) {
// If the item isn't found, return the default (or null)
if (!current[path[i]]) return def;
// Otherwise, update the current value
current = current[path[i]];
}
return current;
};
Browser Compatibility
This works in all modern browsers, and IE9 and up.