1

I've got a data source:

const data = {
  A: [{
    value: 1
  }, {
    value: 2
  }, {
    value: 38
  }],
  B: [{
    value: 46
  }, {
    value: 23
  }, {
    value: 32
  }],
  C: [{
    value: 2345
  }, {
    value: 56
  }, {
    value: 3
  }]
}

I need to transform this object in an array of objects like below:

[{
  A: 1,
  B: 46,
  C: 2345
}, {
  A: 2,
  B: 23,
  C: 56
}, {
  A: 38,
  B: 32,
  C: 3
}]

I made some attempts but still not there:

a)

const result = Object.keys(data).map(key => data[key])

b)

const b = Object.keys(data).reduce((acc, curr, i) => {
  const values = data[curr].map(el => el.value)

  acc[curr] = values[i]

  return acc


}, [])

EDIT:

All arrays (A, B, C) should have the same length. In case it doesn't, value should be "-" for the missing ones

eg:

[{
  A: 1,
  B: 46,
  C: 2345
}, {
  A: 2,
  B: 23,
  C: 56
}, {
  A: 38,
  B: 32,
  C: -
}]
5
  • Will each array (A, B, C, etc.) inside data object have always the same length (for ex: 3 in your question)? Commented Jul 26, 2021 at 8:45
  • Not necessarily. If it does not contain data, it should return "-". I'll add this to the question. thanks Commented Jul 26, 2021 at 8:47
  • what if your data reaches z what will happen then? Commented Jul 26, 2021 at 8:51
  • You've shown three properties, and three entries for each property. But could it be 4 and 2? 5 and 20? Commented Jul 26, 2021 at 8:54
  • Ah, the edit about - tells us (subtly) that it can be the case that the two numbers don't match up. Commented Jul 26, 2021 at 9:11

7 Answers 7

6

Assuming the number of objects in your value arrays is the same (or less than) the number of properties in your data object, you could map your data keys to new objects that you can create using Object.fromEntries(). You can pass an array of mapped values to this fromEntries call, which goes through all your keys a, b, c and uses the current index from the outer loop to determine which object to grab its value from:

const data = { A: [{ value: 1 }, { value: 2 }, { value: 38 }], B: [{ value: 46 }, { value: 23 }, { value: 32 }], C: [{ value: 2345 }, { value: 56 }, { value: 3 }] };

const res = Object.keys(data).map((_, i, arr) => 
  Object.fromEntries(arr.map(key => [key, data[key][i]?.value ?? "-"]))
);

console.log(res);

If you need a more robust approach, I suggest you go with a solution such as T.J. Crowder's that can handle the case where there are more objects in your arrays than there are properties in your object.

Sign up to request clarification or add additional context in comments.

4 Comments

This works for the specific example because it just so happens that there are three values in each array and there are three properties with arrays. I've asked the OP if that's always the case or if it's sometimes 4 and 2 or similar, which sadly would break this elegant solution. (For instance, if I add a fourth element in the A array, its value never shows up in the result.)
@T.J.Crowder hm yes, it would break in that case, thanks for pointing that out. I'd need to think of a different approach if that's the case (which it very well may be)
Sadly the edit about - tells us, quite indirectly, that we can't rely on the property count and value count being the same.
@T.J.Crowder ah, yes, I guess the above tries to handle the case where the array values contain a smaller number of objects than the number of properties, however, it won't handle the case when there are more objects than properties. Your solution handles both cases nicely (I specifically like the let entry = result[index]; part)
1

I'm going to assume that the fact there are three properties (A, B, and C) and the fact there are three values for each of them is a coincidence and that we can't rely on that.

If so, see comments:

// Get the keys
const keys = Object.keys(data);
// A blank object to use as a template for a new array entry w/blank values
const blankEntry = Object.fromEntries(keys.map(key => [key, "-"]));
// Create the result array; since we don't know how long it'll need to
// be without reading through the array, just start with a blank one)
const result = [];
// Loop through the properties
for (const key of keys) {
    // Loop through this property's values
    const values = data[key];
    for (let index = 0; index < values.length; ++index) {
        // Get the object at this index, creating it if not there
        let entry = result[index];
        if (!entry) {
            // Make a shallow copy of the template to create the entry
            result[index] = entry = {...blankEntry};
        }
        // Set this key's value
        entry[key] = values[index].value;
    }
}

Live Example (I've added a fourth entry to A to show that the three and three thing isn't important to the solution, and to show the "-" thing working):

const data = {
  A: [{
    value: 1
  }, {
    value: 2
  }, {
    value: 38
  }, {
    value: 42
  }],
  B: [{
    value: 46
  }, {
    value: 23
  }, {
    value: 32
  }],
  C: [{
    value: 2345
  }, {
    value: 56
  }, {
    value: 3
  }]
};

const keys = Object.keys(data);
const blankEntry = Object.fromEntries(keys.map(key => [key, "-"]));
const result = [];
for (const key of keys) {
    const values = data[key];
    for (let index = 0; index < values.length; ++index) {
        let entry = result[index];
        if (!entry) {
            result[index] = entry = {...blankEntry};
        }
        entry[key] = values[index].value;
    }
}
console.log(result);
.as-console-wrapper {
    max-height: 100% !important;
}

Comments

1

You could get the max length first and then map the values.

const
    data = { A: [{ value: 1 }, { value: 2 }, { value: 38 }, { value: 99 }], B: [{ value: 46 }, { value: 23 }, { value: 32 }], C: [{ value: 2345 }, { value: 56 }, { value: 3 }] },
    entries = Object.entries(data),
    length = entries.reduce((r, [, { length }]) => Math.max(r, length), 0),
    result = entries.reduce(
        (r, [k, a]) =>  r.map((o, i) => ({ ...o, ...(i in a && { [k]: a[i].value }) })),
        Array.from(
            { length },
            () => Object.fromEntries(entries.map(([k]) => [k, '-']))
        )
    );

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

1 Comment

Thanks for this inspired piece of code. I always look forward to reading your answers here, as they always contain somthing new and interesting to pick up!
0

You can create an empty object with - and assign value after with reduce() and forEach().

const data = { A: [ { value: 1, }, { value: 2, }, { value: 38, }, ], B: [ { value: 46, }, { value: 23, }, { value: 32, }, ], C: [ { value: 2345, }, { value: 56, }, { value: 3, }, ], };

const keys = Object.keys(data);

const emptyObj = keys.map(_ =>
  keys.reduce((o, key) => ({ ...o, [key]: "-" }), {})
);

const o = keys.reduce((a, b) => {
  data[b].forEach((el, i) => {
    a[i][b] = el.value;
  });
  return a;
}, emptyObj);

console.log(o);

Comments

0

I think I'd try with something like:

// input
const data = {
  A: [{ value: 1 }, { value: 2 }, { value: 38 }, { value: 57 }],
  B: [{ value: 46 }, { value: 23 }, { value: 32 }, { value: 42 }],
  C: [{ value: 2345 }, { value: 56 }, { value: 3 }],
  D: [{ value: 345 }, { value: 684 }]
};

// I am using an IIFE to initialize `output` in order not to pollute the global scope
// with variables that are not re-used anywhere else but obviously it is not strictly necessary.
const output = (() => {
  const keys = Object.keys(data);
  const maxItemsNr = Math.max(...Object.values(data).map(item => item.length));
  const newArr = [];
  for (let i = 0; i < maxItemsNr; i++) {
      const item = keys.reduce((outObj, key) => {
      outObj[key] = data[key][i] ? data[key][i].value : '-';
      return outObj;
    }, {});
    newArr.push(item);
  }
  return newArr
})();

// test
console.log(output);

Comments

0

And this is my take on it:

const data = { A: [{value: 1}, {value: 2}],
               B: [{value: 46}, {value: 23}, {value: 32}, {value: 38}, {value: 42}],
               C: [{value: 2345}, {value: 56}, {value: 3}] };

const ntrs = Object.entries(data),
      blnk = ntrs.reduce((a,[key])=>(a[key]="-",a),{}),
      rslt = [];
ntrs.forEach(([k,arr])=> arr.forEach(({value},i)=>(rslt[i]=rslt[i]||{...blnk})[k]=value) )
  
console.log(rslt);
.as-console-wrapper {
    max-height: 100% !important;
}

Comments

-1

My solution. But I think Nick Parson's solution is better.

const data = {A: [{value: 1}, {value: 2}, {value: 38}],B: [{value: 46}, {value: 23}, {value: 32}],C: [{value: 2345}, {value: 56}, {value: 3}]}

function transform(object) {
  const res = [];

  Object.keys(object).forEach((key)=> {
    object[key].forEach(({value},i) => {
      if(res[i]) {
        res[i][key] = value
      } else {
        res[i] = {
          [key]: value
        }
      }
    })
  })

  return res
}

console.log(transform(data))

1 Comment

Unfortunately this doesn't do the "-" when there are fewer entries in one of the value arrays than the others.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.