JavaScript: Convert nested array values in object to a flat structure

Is there a way to flatten any array values within an object in JavaScript, not limited to just ecommerce? For example:

var sample = {
  price: "999",
  description: "...",
  ecommerce: {
    products: [
      {
        brand: "apple",
        category: "phone"
      },
      {
        brand: "google",
        category: "services"
      }
    ]
  }
};

I want the output to be:

{
  price: "999",
  description: "..."
  ecommerce: {
    products_1: {
      brand: "apple",
      category: "phone"
    },
    products_2: {
      brand: "google",
      category: "services"
    }
  }
}

What is the most efficient way to achieve this using JavaScript (ES6/7)?

Thank you!

Note: I've tried a solution but it's not working as expected. Is there a better functional approach to this?

function flattenArray(array) {

  var obj = array.reduce((acc, cur, i) => {
    acc[i] = cur;
    return acc;
  }, {});

  return obj;
}

function cleanObject(object) {
  for (let key in object) {
    let testObject = object[key];

    if (Array.isArray(testObject)) {
      testObject = flattenArray(testObject)
    } else if (typeof(testObject) === 'object') {
      testObject = cleanObject(testObject);
    }

    return testObject;
  }

  return object;
}

var clean = cleanObject(sample);

UPDATE: What if the object is structured like this:

var sample = {
  price: "999",
  description: "...",
  differentArray: [ 
    {
      brand: "apple",
      category: "phone"
    },
    {
      brand: "google",
      category: "services"
    }
  ]
};

This time the array is under a different key and nested at a different level.

Answer №1

An iterative implementation using the Array#reduce method is effective for handling key-value bags of this nature. A generic solution may take the following form:

function recursivelyMapArrayItemsToGenericKeys(collector, key) {
  var
    source  = collector.source,
    target  = collector.target,
    type    = source[key];

  if (Array.isArray(type)) {
    type.forEach(function (item, idx) {
      var
        keyList     = Object.keys(item || ''),
        genericKey  = [key, idx].join('_');

      if (keyList.length >= 1) {
        target[genericKey] = keyList.reduce(recursivelyMapArrayItemsToGenericKeys, {

          source: item,
          target: {}

        }).target;
      } else {
        target[genericKey] = item;
      }
    });
  } else if (typeof type !== 'string') {
    var keyList = Object.keys(type || '');

    if (keyList.length >= 1) {
      target[key] = keyList.reduce(recursivelyMapArrayItemsToGenericKeys, {

        source: type,
        target: {}

      }).target;
    } else {
      target[key] = type;
    }
  } else {
    target[key] = type;
  }
  return collector;
}

var sample = {
  price: "999",
  description: "...",
  ecommerce: {
    products: [{
      brand: "apple",
      category: "phone"
    }, {
      brand: "google",
      category: "services"
    }, {
      foo: [{
        brand: "bar",
        category: "biz"
      }, {
        brand: "baz",
        category: "biz"
      }]
    }]
  }
};

var result = Object.keys(sample).reduce(recursivelyMapArrayItemsToGenericKeys, {

  source: sample,
  target: {}

}).target;

console.log('sample : ', sample);
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }

During a subsequent code refactoring phase, redundant logic can be removed with two separate functions and alternating recursive calls.

function recursivelyAssignItemsFromTypeByKeys(target, type, keyList, key) {
  if (keyList.length >= 1) {
    target[key] = keyList.reduce(recursivelyMapArrayItemsToGenericKeys, {

      source: type,
      target: {}

    }).target;
  } else {
    target[key] = type;
  }
}

function recursivelyMapArrayItemsToGenericKeys(collector, key) {
  var
    source  = collector.source,
    target  = collector.target,
    type    = source[key];

  if (Array.isArray(type)) {
    type.forEach(function (item, idx) {
      var
        keyList     = Object.keys(item || ''),
        genericKey  = [key, idx].join('_');

      recursivelyAssignItemsFromTypeByKeys(target, item, keyList, genericKey);
    });
  } else if (typeof type !== 'string') {
    var keyList = Object.keys(type || '');

    recursivelyAssignItemsFromTypeByKeys(target, type, keyList, key);
  } else {
    target[key] = type;
  }
  return collector;
}

var sample = {
  price: "999",
  description: "...",
  ecommerce: {
    products: [{
      brand: "apple",
      category: "phone"
    }, {
      brand: "google",
      category: "services"
    }, {
      foo: [{
        brand: "bar",
        category: "biz"
      }, {
        brand: "baz",
        category: "biz"
      }]
    }]
  }
};

var result = Object.keys(sample).reduce(recursivelyMapArrayItemsToGenericKeys, {

  source: sample,
  target: {}

}).target;

console.log('sample : ', sample);
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }

Answer №2

My plan of action would be:

const data = {
  price: "999",
  description: "...",
  ecommerce: {
    products: [
      {
        brand: "apple",
        category: "phone"
      },
      {
        brand: "google",
        category: "services"
      }
    ]
  }
}

data.ecommerce.products.forEach((product, index) => {
  data.ecommerce['products_' + index] = product
})

delete data.ecommerce.products

console.log(data)

Answer №3

Easily transform the ecommerce array by using the reduce method and destructuring each element as needed.

const data = {
  price: "999",
  description: "...",
  ecommerce: {
    products: [
      {
        brand: "apple",
        category: "phone"
      },
      {
        brand: "google",
        category: "services"
      }
    ]
  }
}

const flattenAndRename = (arr, key) =>
  arr.reduce((prev, curr, index) => {
    return {
      ...prev,
      [`${key}_${index + 1}`]: curr,
    }
  }, {})
  
const modifiedData = {
  ...data,
  ecommerce: flattenAndRename(data.ecommerce.products, 'products'),
}

console.log(modifiedData)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to determine if a user is actively reading a page using Javascript?

As I work on creating a webpage that utilizes AJAX polling to bring dynamic content into the view, I have come across a challenge. With the page JavaScript constantly downloading updated information and rendering it while users are engaged with other conte ...

Using Javascript to extract a custom attribute from a button element

In my HTML, there is a button that has a custom property called data-filter-params: <button class="button" type="submit" data-filter-params="{'Type', 'Filter.Type'},{'Code','Filter.Code'}" >Filter</button&g ...

What is the best way to add a picture using React and Next.js?

Being a novice in React and Next, I recently embarked on a project that involves uploading a profile picture. However, every time I try to upload an image, an error pops up. Error: The src prop (http://localhost:3333/files/ SOME IMAGE.jpg) is invalid on n ...

Mastering state transitions in Angular JS

Currently, I am developing code to display a simple list of users. Upon clicking on a user from the list, I aim to navigate to a view containing detailed information about that particular user. At this stage, I have successfully implemented the functionali ...

Preventing Repeated Clicks in AngularJS

Looking for a better approach to handle double clicks in AngularJS other than using ng-disabled. I have a solution to disable double/multi-click functionality on button click and re-enable it after completing an ajax process. Any suggestions? When our cod ...

Steps to store user input into an array and subsequently combine the stored input:

I am currently working on a form that consists of two text boxes: Task and Description. My goal is to be able to log the input from both boxes and save it via a submit button. For example: Task: do laundry Description: do a buttload of laundry (idk lol) I ...

Parsing improperly formatted JSON from an HTTP GET request can be done using either AngularJS or JQuery

Trying to decipher a poorly formatted JSON response from a remote server that looks something like this: //[ {},{} ] In my AngularJS code: $http.get('http://www.example.com/badjson') .success(function(data) { console.log(data); }) ...

Retrieving Information from API using Vue.js

In the code snippet below, I am displaying data from an API for all flats on a single page. However, I am facing difficulty in showing the floor number for each flat. The JSON body is as follows: { "response": [ { "fl ...

Getting undefined while trying to iterate through data on a next js page using getStaticProps. What could be causing this issue?

Encountering difficulties while trying to output the page meta data in next.js when I execute: npm run build An error is thrown with the following message: Error occurred prerendering page "/blog/[...slug]". Read more: https://err.sh/next.js/pre ...

How can you assign a strokeStyle color to a Canvas using a CSS property?

Our team is currently working on an Angular2 / Ionic 2 project where we have implemented a HTML Canvas element that allows users to draw on it. One challenge we are facing is how to set the Canvas strokeStyle property using a color provided by a CSS style. ...

Divide the number by the decimal point and then send it back as two distinct parts

Let's tackle a challenging task. I have a price list that looks like this: <span class="price">10.99€/span> <span class="price">1.99€/span> My goal is to transform it into the following format: <span class="price">10< ...

What is the common approach for directing a setState Redux action?

I am looking to streamline my state update actions in multiple react-redux reducers by creating a general setState action. This will allow me to have consistent syntax for dispatching both local and redux scope updates. For local updates, I would use this. ...

One way to display GIFs sequentially without preloading them is by using JavaScript

On my website, I'm trying to display five animated gifs sequentially. I want gif2 to appear and start its animation only after gif1 has finished its animation. However, the code I'm using is giving me some unexpected behavior. While it works corr ...

Switch the position of the Refer a friend button and the name button to the right

I am seeking assistance to align two buttons to the right. const getFullName = () => { if (authContext.user.first_name) { return `${authContext.user.first_name} ${authContext.user.last_name}`; } return authContext.use ...

Can Javascript (PWA) be used to detect fake GPS or mock GPS in applications?

Looking for a solution to prevent users from using Fake Location tools in my PWA application that gathers absence location data. Is there a method or package in JavaScript to detect the presence of Fake GPS installed on the device? ...

Canceling a window in JSP and navigating back to the previous page using JavaScript

Here is my Java class controller: public class Controller extends HttpServlet { private Chooser chooser = Chooser.INSTANCE; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOExcep ...

Issue with Datatables FixedColumns plugin in IE8/7 and Firefox

I am encountering issues with the FixedColumn plugin on IE8, IE7, and Firefox. All of them are causing my table to malfunction, displaying the following error: Error: Invalid argument. Line: 566 Char: 3 Code: 0 URI: ./resources/javascript/dataTable/Fixed ...

Going back to the beginning of a for loop in the C programming language

Despite the fact that this query has been posed countless times, I still haven't come across a satisfactory answer to address my specific situation. Either the solutions available don't fit my needs, or I'm simply unable to see where I' ...

Changing the color variable of an object using an onClick function in JavaScript

I'm currently working on a simple game where users can draw using the keys W, A, S, and D. If you want to take a look at the progress I've made so far, here is a JSFiddle link. There's a collision function in the code that I no longer need, ...

Tips for transferring information to a textarea within a bootstrap modal by selecting a cell in a table

I need to enable users to edit information in a table. One cell in the table contains text. Here is an excerpt from the table: <table class="table table-striped table-hover" id="table_id"> <thead> <tr> <th>Event</th& ...