Skip to content

Edvins Antonovs

Get deeply nested object in JavaScript code challenge

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.

1const john = {
2 profile: {
3 name: { firstName: 'John', lastName: 'Doe' },
4 age: 20,
5 gender: 'Male',
6 },
7};
8
9const jane = {
10 profile: {
11 age: 19,
12 gender: 'Female',
13 },
14};
15
16function getFirstName(user) {
17 return user.profile.name.firstName;
18}

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 is undefined.

Examples

1get(john, 'profile.name.firstName'); // 'John'
2get(john, 'profile.gender'); // 'Male'
3get(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.

1export default function get(object, path, defaultValue) {
2 if (!Object.keys(object).length) return defaultValue;
3
4 const pathArray = Array.isArray(path) ? path : path.split('.');
5
6 let result = object;
7
8 for (let i = 0; i < pathArray.length; i++) {
9 const value = pathArray[i];
10
11 if (typeof result === 'object' && result !== null && value in result) {
12 result = result[value];
13 } else {
14 return defaultValue;
15 }
16 }
17
18 return result;
19}

Appendix

© 2024 by Edvins Antonovs. All rights reserved.