This is a very specific problem but I have an object with arrays:
let rawData = {
one: ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
two: [1,2,3,1,2,3,1,2,3],
three: [1,4,9,3,9,7,6,5,5]
}
and I want to write a generic function that will create a set of nested objects based on the first array (here a, but I'd like to make this function not dependent on a specific key.
In my first try I wrote a function that parses this into an array of objects:
dataframe_for_plot = (data) => {
var length
var names = []
for (var name in data) {
if (data.hasOwnProperty(name))
names.push(name);
length = rawData[name].length;
}
var results = [];
var item;
for (var row = 0; row < length; row ) {
item = {};
for (var col = 0; col < names.length; col ) {
item[names[col]] = data[names[col]][row];
}
results.push(item);
}
return results;
}
Which yeilds
dataframe_for_plot(rawData)
[
{one: "a", two: 1, three: 1},
{one: "a", two: 2, three: 4},
{one: "a", two: 3, three: 9},
{one: "b", two: 1, three: 3},
{one: "b", two: 2, three: 9},
{one: "b", two: 3, three: 7},
{one: "c", two: 1, three: 6},
{one: "c", two: 2, three: 5},
{one: "c", two: 3, three: 5},
]
But I want to build on this function to get my desired output where the first key is used to create objects based on the unique number of that key (3 in this case), and a new value
key is created to contain a nested array of the other keys (two
and three
in this case, grouped together by one
)
let desired_output = [
{
one:"a",
values: [
{two:1, three:1},
{two:2, three:4},
{two:3, three:9}
],
},
{
one:"b",
values: [
{two:1, three:3},
{two:2, three:9},
{two:3, three:7}
],
},
{
one:"c",
values: [
{two:1, three:6},
{two:2, three:5},
{two:3, three:5}
],
}
]
I think my function is a good start, but I need a little help working on the nested part 2! Thanks!
CodePudding user response:
Convert the object to an array of entries, and get the 1st item's key
and vals
. Reduce the vals
to a Map. The Map would have a single object for each unique value from the 1st array if values (one
in your case). Then iterate the array of the other values, take the respective value from each [key, arr]
pair, and convert them to an object using Object.fromEntries()
.
Convert the Map values iterator back to an array using Array.from()
.
const fn = obj => {
const [[key, vals], ...values] = Object.entries(obj)
return Array.from(vals.reduce((acc, v, i) => {
if(!acc.has(v)) acc.set(v, { [key]: v, values: [] })
acc.get(v).values.push(Object.fromEntries(
values.map(([k, arr]) => [k, arr[i]])
))
return acc
}, new Map()).values())
}
const rawData = {
one: ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
two: [1,2,3,1,2,3,1,2,3],
three: [1,4,9,3,9,7,6,5,5]
}
const result = fn(rawData)
console.log(result)
If you want to control the order of properties, you can pass an order
array, and then create the entries manually in the order you've stated:
const fn = (order, obj) => {
const [[key, vals], ...values] = order.map(key => [key, obj[key]])
return Array.from(vals.reduce((acc, v, i) => {
if(!acc.has(v)) acc.set(v, { [key]: v, values: [] })
acc.get(v).values.push(Object.fromEntries(
values.map(([k, arr]) => [k, arr[i]])
))
return acc
}, new Map()).values())
}
const rawData = {
one: ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
two: [1,2,3,1,2,3,1,2,3],
three: [1,4,9,3,9,7,6,5,5]
}
const result = fn(['one', 'three', 'two'], rawData)
console.log(result)