JavaScript: obtaining object depth

Contents

Introduction

In this post we will check one possible approach to calculate the maximum object depth, in JavaScript.

It is important to mention that there are many ways of getting the maximum depth of an object. This Stack Overflow thread, for example, for shows a lot of approaches, many of them resorting to recursive function calls. In this post we are going to follow a different approach, by making use of the flat library.

The concept of depth may also vary depending on the application needs. Here we will be assuming that an empty object has a depth of 1 and an object with a non-nested property also has a depth of 1. If an object has a property that corresponds to another object, and that second object has a non-nested property, then we consider a depth of 2, and so on.

The objective of this post is not to provide a definitive function that covers all the corner cases but rather a simple possible approach that can then be adapted to particular use cases. As such, I did not do extensive testing to all possible scenarios and the function may give unexpected results in some of those scenarios.

Note that here we are applying this logic to a JavaScript object but, in essence, this can be more generically used to get the depth of a JSON. We simply need to parse our JSON string to an object, as shown here.

The tests shown below were performed on Windows 8.1, using version 15.2 of Node.js.

Using the object flat approach

In a previous tutorial, we saw how we could flatten a nested JavaScript object with the flat library. In short, when we apply the flat function to an object, we will obtain a new object that is only one level deep, independently on how deep the original object was. In the new object we obtain, the names of the properties correspond to the full path of each property in the original object.

In each property of the flattened object, the levels of depth are split by a “.”. Nonetheless, this is simpler to visualize with a practical example. Let’s assume we have the following object:

```{
street: "st. street",
code: "2321-12"
}
}
```

After flattening, we would get the following:

```{
}
```

Like mentioned, the levels of depth of each leaf of the object are split by a “.”, which means that if we split the property by “.” and count how many segments we get, we implicitly get the depth of that property. Then, it is just a matter of comparing the depth of all the properties and finding the maximum.

Getting the object depth

We will start by importing the flatten function from the flat package.

```const flatten = require('flat').flatten;
```

Then, we will proceed with the definition of our function to get the object depth. We will call it getObjectDepth,

```function getObjectDepth(obj){
// Implementation
}
```

The first thing we will do is checking if what we have received as input of our function is indeed an object and it is not null. In case some of this validations fails, we will simply return 0.

```if (typeof obj !== "object" || obj === null) {
return 0;
}
```

After this, we will obtain the flattened version of our object, simply by calling the flatten function and passing as input the object.

```const flat = flatten(obj);
```

After this, we will obtain a list with the names of all the keys of our flattened object. This can be done with a call to the keys method of the Object class, passing as input the object to which we want to obtain the keys.

``` const keys =  Object.keys(flat);
```

If our object did not have any keys, then we can immediately return the depth 1.

```if(keys.length === 0){
return 1;
}
```

Now we are going to iterate through all the names of the keys. Then, per each key name (each key name is a string), we will split it by the “.” character, by calling the split method.

The split method returns a list with all the substrings resulting from the split. In order words, each element of the list will correspond to a depth level in the path to the leaf of the object. This means that the length of that list corresponds to the depth of that leaf.

So, we are going create a second list that will contain the depths of all the leafs of the object.

For a more compact code, I’ll be using the map method over the list of key names to obtain the list of depths.

```const depthOfKeys = keys.map(key => key.split(".").length);
```

Naturally, this could have been done with a simple for loop instead, like in the snippet below.

``` const depthOfKeys = [];

for(let key of keys){
const length = key.split(".").length;
depthOfKeys.push(length);
}
```

To finalize the implementation of the function, we simply need to return the maximum value of the elements of the list. To do so, we can use the max method of the Math global object. Since this method calculates the maximum of all the values passed as individual parameters and we actually have a list, we can use the spread operator to expand the elements of the list to individual parameters for the method call.

```return Math.max(...depthOfKeys);
```

The complete code can be seen below. After the function definition, we are calling it over an object with a depth of 2 levels.

```const flatten = require('flat').flatten;

function getObjectDepth(obj){

if (typeof obj !== "object" || obj === null) {
return 0;
}

const flat = flatten(obj);

const keys =  Object.keys(flat);

if(keys.length === 0){
return 1;
}

const depthOfKeys = keys.map(key => key.split(".").length);

return Math.max(...depthOfKeys);
}

const testObj = {
name: "John",
age: 10,
street: "St street",
code: "1234-135"
}
}

console.log(getObjectDepth(testObj ));
```

To test the code, simply run it in a tool of your choice. In my case, I’m using Visual Studio Code with the Code Runner extension.

To finalize our tutorial, we will enhance the function from the previous section to return some additional information besides the maximum object depth. In particular, we will also return the path to the property and its value. If there is more than one property at the maximum depth, then our function will return the first one found, but the code could also be easily adapted to return all the properties at the deepest level.

The code will be very similar to what we have done before, except that the return value will now be an object with the maximum object depth, the name of the property in the flattened object (which corresponds to the path in the original object) and the value of the property. We will initialize it with defaults like shown below:

```let deepest = {
depth: 1,
key: null,
value: null
};
```

Then, when we are iterating through the keys of the flattened object to split them and obtain their length, we compare the depth of that key against the maximum depth key we found so far and, if the current one is bigger, we store not only the new depth but also the property and the value.

```for(let key of keys){
const length = key.split(".").length;

if(length > deepest.depth){
deepest = {
depth: length,
key: key,
value: flat[key]
}
}
}

return deepest;
```

The complete code can be seen in the snippet below, which also includes the application of the function to an object with 4 levels of depth.

```const flatten = require('flat').flatten;

function getObjectDepth(obj){

if (typeof obj !== "object" || obj === null) {
return 0;
}

const flat = flatten(obj);

const keys =  Object.keys(flat);

let deepest = {
depth: 1,
key: null,
value: null
};

if(keys.length === 0){
return deepest;
}

for(let key of keys){
const length = key.split(".").length;

if(length > deepest.depth){
deepest = {
depth: length,
key: key,
value: flat[key]
}
}
}

return deepest;
}

const testObj = {
name: "John",
age: 10,
street: "St street",
code: "1234-135"
},
deepNested: {
x:{
y:{
z:"deepest leaf"
}
}
}
}

console.log(getObjectDepth(testObj));
```

Upon running the code, we obtain the result shown in figure 3. As can be seen, we obtained a maximum property depth of 4, the name and the value of that property, as expected.