Coder Perfect

How do I work with nested objects, arrays, and JSON?

Problem

I’ve got a layered data structure with objects and arrays in it. How can I access a specific or numerous values (or keys) in order to retrieve the information?

For example:

var data = {
    code: 42,
    items: [{
        id: 1,
        name: 'foo'
    }, {
        id: 2,
        name: 'bar'
    }]
};

What is the best way to get the name of the second item in items?

Asked by Felix Kling

Solution #1

There is just one data type in JavaScript that can hold multiple values: Object. A special type of object is an array.

Objects of the form (Plain)

{key: value, key: value, ...}

The form of an array is

[value, value, ...]

A key -> value structure is exposed by both arrays and objects. In an array, keys must be numeric, however in objects, any string can be used as a key. The key-value pairs are also called the “properties”.

Dot notation can be used to access properties.

const value = obj.someProperty;

If the property name is not a valid JavaScript identifier name [spec], or if the name represents the value of a variable, use bracket notation:

// the space is not a valid character in identifier names
const value = obj["some Property"];

// property name as variable
const name = "some Property";
const value = obj[name];

As a result, only bracket notation can be used to access array elements:

const value = arr[5]; // arr.5 would be a syntax error

// property name / index as variable
const x = 5;
const value = arr[x];

JSON, like XML, YAML, CSV, and others, is a textual representation of data. To use such data, it must first be transformed to JavaScript data types, such as arrays and objects (and how to work with those was just explained). The question Parse JSON with JavaScript? explains how to parse JSON.

Because accessing arrays and objects is core JavaScript knowledge, it is recommended that you read the MDN JavaScript Guide, particularly the parts on arrays and objects.

An array or object that references to additional arrays or objects is referred to as a nested data structure, and its values are arrays or objects. Such structures can be accessed by using dot or bracket notation in a specific order.

Here’s an illustration:

const data = {
    code: 42,
    items: [{
        id: 1,
        name: 'foo'
    }, {
        id: 2,
        name: 'bar'
    }]
};

Let’s pretend we’re looking for the second item’s name.

Here’s how we can do it one step at a time:

Because data is an object, we may use dot notation to access its properties. The items property can be accessed in the following way:

data.items

Because the value is an array, we must use bracket notation to access its second element:

data.items[1]

We utilize dot notation to access the name property of this value, which is an object. As a result, we get:

const item_name = data.items[1].name;

For any of the characteristics, we may have used bracket notation instead, especially if the name had characters that would have rendered it unsuitable for dot notation:

const item_name = data['items'][1]['name'];

When you get undefined, it’s usually because the object/array doesn’t have a property with that name.

const foo = {bar: {baz: 42}};
console.log(foo.baz); // undefined

Inspect the structure of the object / array using console.log or console.dir. It’s possible that the property you’re looking for is declared on a nested object or array.

console.log(foo.bar.baz); // 42

We can use the for…in [MDN] loop for objects and the for [MDN] loop for arrays to cycle through all properties / elements if the property names are unknown or if we wish to access all properties of an object / elements of an array.

Objects

We can iterate over the object as follows to iterate over all data properties:

for (const prop in data) {
    // `prop` contains the name of each property, i.e. `'code'` or `'items'`
    // consequently, `data[prop]` refers to the value of each property, i.e.
    // either `42` or the array
}

Depending on where the object came from (and what you want to accomplish), you may need to test whether a property is truly a property of the object or an inherited property in each iteration. This may be accomplished with Object#hasOwnProperty [MDN].

You can acquire an array of property names using Object.keys [MDN] instead of for…in with hasOwnProperty:

Object.keys(data).forEach(function(prop) {
  // `prop` is the property name
  // `data[prop]` is the property value
});

Arrays

A for loop is used to go over all entries of the data.items array:

for(let i = 0, l = data.items.length; i < l; i++) {
    // `i` will take on the values `0`, `1`, `2`,..., i.e. in each iteration
    // we can access the next element in the array with `data.items[i]`, example:
    // 
    // var obj = data.items[i];
    // 
    // Since each element is an object (in our example),
    // we can now access the objects properties with `obj.id` and `obj.name`. 
    // We could also use `data.items[i].id`.
}

To iterate through arrays, one might also use for…in, however there are several reasons why this should be avoided: Why is it considered bad practice in JavaScript to use ‘for(var item in list)’ with arrays?

With more browsers supporting ECMAScript 5, the array function forEach [MDN] is also becoming a viable option:

data.items.forEach(function(value, index, array) {
    // The callback is executed for each element in the array.
    // `value` is the element itself (equivalent to `array[index]`)
    // `index` will be the index of the element in the array
    // `array` is a reference to the array itself (i.e. `data.items` in this case)
}); 

You can also use the for…of [MDN] loop in environments that support ES2015 (ES6), which works for any iterable and not just arrays:

for (const item of data.items) {
   // `item` is the array element, **not** the index
}

There is no “index” to access or use in each iteration; for…of just delivers us the next member of the iterable.

In addition to unknown keys, the data structure’s “depth” (i.e. how many nested objects it contains) may also be unknown. The exact data structure determines how to access deeply nested properties.

However, if the data structure involves recurring patterns, such as a binary tree representation, the method usually entails repeatedly [Wikipedia] accessing each level of the data structure.

Here’s an example of how to find the binary tree’s first leaf node:

function getLeaf(node) {
    if (node.leftChild) {
        return getLeaf(node.leftChild); // <- recursive call
    }
    else if (node.rightChild) {
        return getLeaf(node.rightChild); // <- recursive call
    }
    else { // node must be a leaf node
        return node;
    }
}

const first_leaf = getLeaf(root);

Testing the type of the value and acting accordingly provides a more generic technique to access a layered data structure with unknown keys and depth.

Here’s an example of a hierarchical data structure that adds all primitive values to an array (assuming it does not contain any functions). If we come across an object (or an array), we just use toArray on that value again (recursive call).

function toArray(obj) {
    const result = [];
    for (const prop in obj) {
        const value = obj[prop];
        if (typeof value === 'object') {
            result.push(toArray(value)); // <- recursive call
        }
        else {
            result.push(value);
        }
    }
    return result;
}

We can analyze the value at each stage to decide how to proceed because the structure of a complicated object or array is not always evident. We may achieve this with the aid of console.log [MDN] and console.dir [MDN]. For instance (from the Chrome console):

> console.log(data.items)
 [ Object, Object ]

We can see that data.items is an array with two members, each of which is an object. The objects can even be opened and inspected right away in the Chrome console.

> console.log(data.items[1])
  Object
     id: 2
     name: "bar"
     __proto__: Object

This tells us that data.items[1] is an object, and we can see that it has three properties: id, name, and __proto__ after expanding it. The latter is an internal attribute for the object’s prototype chain. However, the prototype chain and inheritance are beyond the scope of this response.

Answered by 25 revs, 6 users 92%

Solution #2

You have access to

data.items[1].name

or

data["items"][1]["name"]

Both options are equally valid.

Answered by vitmalina

Solution #3

There are many built-in methods in objects and arrays that can assist you with data processing.

I’m utilizing arrow functions in a lot of the examples. They’re similar to function expressions in that they lexically bind this value.

Object.keys(), Object.values(), and Object.entries() all return an array of object keys and values in the format [key, value].

With a for-of loop and destructuring assignment, it’s quite easy to traverse the result of Object.entries().

The for-of loop is used to traverse across array elements. The syntax is (const element of array) (var or let can be used in place of const, but it’s better to use const if we don’t want to edit the element).

You can extract values from an array or an object and assign them to variables using destructuring assignment. Const [key, value] signifies that instead of assigning the [key, value] array to element, we assign the first element to key and the second element to value in this case. It’s the same as this:

for (const element of Object.entries(obj)) {
  const key = element[0]
       ,value = element[1]
}

Destructuring, as you can see, makes this a lot easier.

If the supplied callback function returns true for each element of the array, the every() method returns true. If the supplied callback function returns true for some (at least one) element, the some() method returns true.

The find() method delivers the first element that matches the callback function supplied. The filter() method returns an array containing all elements that satisfy the callback function specified.

The map() method returns an array containing the results of invoking a callback function on the array components provided.

By invoking the specified callback function with two members, the reduce() method lowers an array to a single value.

The starting value is an optional second parameter for the reduce() function. When the array on which reduce() is called can have zero or one elements, this is advantageous. For example, if we wanted to create a function sum() which takes an array as an argument and returns the sum of all elements, we could write it like that:

Answered by Michał Perłakowski

Solution #4

If you need to retrieve an item from the example structure by id or name without knowing its position in the array, the underscore.js library is the quickest way to do so:

var data = {
    code: 42,
    items: [{
        id: 1,
        name: 'foo'
    }, {
        id: 2,
        name: 'bar'
    }]
};

_.find(data.items, function(item) {
  return item.id === 2;
});
// Object {id: 2, name: "bar"}

Using higher order functions instead of for or for..in loops, in my experience, produces code that is easier to reason about and hence more maintainable.

Just my two cents’ worth.

Answered by holographic-principle

Solution #5

At times, accessing a nested object using a string can be desirable. The simple approach is the first level, for example

var obj = { hello: "world" };
var key = "hello";
alert(obj[key]);//world

However, with sophisticated json, this isn’t always the case. As json becomes more complex, the methods for locating values within it become more complicated as well. For traversing the json, a recursive technique is recommended, and how that recursion is used will depend on the type of data being searched for. When conditional statements are present, a json search might be a useful tool.

If the attribute to be accessed is already known but the path to it is complicated, like in this object,

var obj = {
 arr: [
    { id: 1, name: "larry" },    
    { id: 2, name: "curly" },
    { id: 3, name: "moe" }
 ]
};

And you know you want to obtain the array’s first result in the object; perhaps you should use

var moe = obj["arr[0].name"];

However, this will result in an exception because there is no object property with that name. Flattening the tree aspect of the object would be the solution to be able to use it. This can be done in a recursive manner.

function flatten(obj){
 var root = {};
 (function tree(obj, index){
   var suffix = toString.call(obj) == "[object Array]" ? "]" : "";
   for(var key in obj){
    if(!obj.hasOwnProperty(key))continue;
    root[index+key+suffix] = obj[key];
    if( toString.call(obj[key]) == "[object Array]" )tree(obj[key],index+key+suffix+"[");
    if( toString.call(obj[key]) == "[object Object]" )tree(obj[key],index+key+suffix+".");   
   }
 })(obj,"");
 return root;
}

The complicated object can now be flattened.

var obj = previous definition;
var flat = flatten(obj);
var moe = flat["arr[0].name"];//moe

This strategy is demonstrated using jsFiddle.

Answered by Travis J

Post is based on https://stackoverflow.com/questions/11922383/how-can-i-access-and-process-nested-objects-arrays-or-json