4

This is analogous to my actual problem but i feel illustrates the problem i need to solve without complicating things.

I need to create an array of objects by iterating through a 2d array. The 2d array looks something like the"sampleArray" below.

let sampleArray = [ 
  ["open",2], 
  ["high",3], 
  ["low",5], 
  ["close", 10], 
  ["volume", 20], 
  ["open",20], 
  ["high",30], 
  ["low",50], 
  ["close", 100], 
  ["volume", 21],
  ["open",21], 
  ["high",33], 
  ["low",51], 
  ["close", 1], 
  ["volume", 2],
  ["open",7], 
  ["high",8], 
  ["low",5], 
  ["close", 11], 
  ["volume", 22]
 ];

I'm currently formatting a particularly ugly set of API data and have flattened the data down to a 2d array. What i need now is to put these values into an array of 5 objects where each object's data property is populated by all of the values with that particular label.

newObject = [
    {
     data: [2,20,21, ...]
     label: "open"  
    }, 
    {
     data: [3,50, ...]
     label: "high"
    }, 
    {
     data: [etc...]
     label: "low"  
    }, 
    {
     data: [etc...]
     label: "close"  
     }, 
    {
     data: [etc...]
     label: "volume"  
     }
]

I'm about 3 hours into trying to make this work and feel that I'm going about this all wrong, I keep getting an error that newArray[i]["label"] is undefined, I get why it's happening (no object with that property exists YET, I'm just not sure how to express that idea (If the object with that label doesn't exist yet, create it and add that matching value to it's value property) in javascript.

function makeArray(array) {
  let newArray = [];
  for(let i = 0; i <= array.length; i++){
    if(newArray[i]["label"] !== array[i][0]){
      newArray.push({
        "label": array[i][0],
        "value": array[i][1]
      })
    }    
    else{
      newArray.push({
        "value": array[i][1]
      })
    }
  }
return newArray;
}

let test = makeArray(sampleArray);
console.log(test);

Sorry for the long post, I've been flattening this nightmare API all day and feel like I'm just running in place at this point. I would much prefer a point in the right direction than an outright answer. I'm gonna hit the sack and see if someone can provide me with a little perspective by morning. Thank you!

Jon Miles
  • 9,605
  • 11
  • 46
  • 66

3 Answers3

5

You can use reduce

let sampleArray = [
  ["open", 2],
  ["high", 3],
  ["low", 5],
  ["close", 10],
  ["volume", 20],
  ["open", 20],
  ["high", 30],
  ["low", 50],
  ["close", 100],
  ["volume", 21],
  ["open", 21],
  ["high", 33],
  ["low", 51],
  ["close", 1],
  ["volume", 2],
  ["open", 7],
  ["high", 8],
  ["low", 5],
  ["close", 11],
  ["volume", 22]
];


let newObject = Object.values(sampleArray.reduce((c, [n, v]) => {
  c[n] = c[n] || {label: n,data: []};
  c[n].data.push(v);
  return c;
}, {}));

console.log(newObject);
Eddie
  • 26,593
  • 6
  • 36
  • 58
  • Wow! what an awesome solution! Thank you so much! I knew I had to use a reducer function but I was really struggling to understand what they do exactly. I feel like I finally have a cursory understanding of the reducer function through trying to reverse-engineer your solution. Thanks again! – David Arias Mar 11 '18 at 05:55
  • 1
    Happy to help @DavidArias – Eddie Mar 11 '18 at 05:56
  • Basically what `reduce` does is, it summarizes the array into an object. The use `Object.values` to convert the object to an array for final output. Let me know if you have a question. :) – Eddie Mar 11 '18 at 06:20
2

Using Vanilla JS:

let sampleArray = [
  ["open", 2],
  ["high", 3],
  ["low", 5],
  ["close", 10],
  ["volume", 20],
  ["open", 20],
  ["high", 30],
  ["low", 50],
  ["close", 100],
  ["volume", 21],
  ["open", 21],
  ["high", 33],
  ["low", 51],
  ["close", 1],
  ["volume", 2],
  ["open", 7],
  ["high", 8],
  ["low", 5],
  ["close", 11],
  ["volume", 22]
];

function transform(sampArr) {
  let obj = {};                                 // A map equivalent to store repeated value
  
  // Loop through each array within array
  sampArr.forEach(function(arr) {
    if (!obj[arr[0]]) {
      obj[arr[0]] = {};                         // Instantiate the map the first time
    }

    if (!obj[arr[0]].data) {
      obj[arr[0]]["data"] = [];                 // First time instantiate the array
    }

    obj[arr[0]].data.push(arr[1]);              // Push the repeated values
  });


  // Create the structure you want
  return Object.keys(obj).map(function(key) {
    return {
      data: obj[key].data,
      label: key
    };
  });
}

console.log(transform(sampleArray));
Abhijit Kar ツ
  • 1,732
  • 1
  • 11
  • 24
  • I'm still struggling to understand this, but this is what I have so far, please correct me if I'm wrong. In the forEach function you're creating the object's per the amount of labels, if the object's don't exist, then you're creating the data arrays for each object and pushing the values to them. then in the map step you're positioning all of the data objects into the resulting array and also passing in the values for 'label"? – David Arias Mar 11 '18 at 06:11
  • @DavidArias, Yup, as simple as that. You got it. – Abhijit Kar ツ Mar 11 '18 at 06:27
1

You can introduce a temperary variable before going to final value as -

const tempObject = sampleArray.reduce((acc, arr) => {
  if (acc.hasOwnProperty(arr[0]) {
    return acc[arr[0]].push(arr[1]);
  }
  return { ...acc, { [arr[0]]: [arr[1]] };
}), {});

Now tempObject will be -

{
  'open': [2, 20, 21, ...],
  'high': [3, 50, ...],
  'low': [etc, ...],
  'close': [etc, ...],
  'volume': [etc, ...]
}

Then you can eterate to above object as,

const finalData = Object.keys(tempObject).map(key => (
  {
    data: tempObject[key],
    label: key
  }
);

Now finalData will be the desired object.