Reputation: 1
I am confused with how to make a nested array of object in javascript and I came here hoping that someone could help. Here is my raw data that I currently have :
[
[
{ trait_type: 'Type:', value: 'Epic' },
{ trait_type: 'Clan:', value: 'Vampire' },
],
[{ trait_type: 'Rare Accessory:', value: 'Rainbow Glass' }],
[{ trait_type: 'Rarity:', value: 'Common' }],
[{ trait_type: 'Type:', value: 'Common' }],
[{ trait_type: 'Type:', value: 'Rare' }],
[{ trait_type: 'Type:', value: 'Epic' }, { trait_type: 'Rarity:', value: 'Common' }],
]
And I want to process that data into something like this :
[
{ "Type:":
[
{"Epic": 2},
{"Common": 1},
{"Rare": 1}
]
},
{"Clan:":
[
{"Vampire" : 1}
]
},
{"Rare Accessory:":
[
{"Rainbow Glass" : 1}
]
},
{"Rarity:":
[
{"Common" : 2}
]
}
]
The number on the output is the count where the value appears in that data. Appreciate every input and help. Thanks
Upvotes: 0
Views: 866
Reputation: 24638
Start off by flattening the array using the .flat()
method, then use the .reduce()
method to summarize the data. You will end up with an object; since the keys are unique this would be a preferred format to leave the result. To convert it into your desired array use .map()
with Object.entries()
and Object.fromEntries()
as follows:
const data = [
[{ trait_type: 'Type:', value: 'Epic' },{ trait_type: 'Clan:', value: 'Vampire'},
],
[{ trait_type: 'Rare Accessory:', value: 'Rainbow Glass' }],
[{ trait_type: 'Rarity:', value: 'Common' }],
[{ trait_type: 'Type:', value: 'Common' }],
[{ trait_type: 'Type:', value: 'Rare' }],
[{ trait_type: 'Type:', value: 'Epic' }, { trait_type: 'Rarity:', value: 'Common' }],
];
const summary = data
//flatten the array
.flat()
//summarize
.reduce((acc,cur) => ({
...acc,
...{
[cur.trait_type]: {
...acc[cur.trait_type],
...{
[cur.value]: (acc[cur.trait_type] && acc[cur.trait_type][cur.value] || 0) + 1
}
}
}
}), {});
console.log( summary );
//I would leave it ^ this way
//But to get to you desired result here we go V
const desired = Object.entries(summary)
.map(e => Object.fromEntries([e]));
console.log( desired );
Upvotes: 0
Reputation: 1198
Here is one approach to get the desired result:
const data = [
[ { trait_type: 'Type:', value: 'Epic' }, { trait_type: 'Clan:', value: 'Vampire' } ],
[ { trait_type: 'Rare Accessory:', value: 'Rainbow Glass' }],
[ { trait_type: 'Rarity:', value: 'Common' } ],
[ { trait_type: 'Type:', value: 'Common' } ],
[ { trait_type: 'Type:', value: 'Rare' } ],
[ { trait_type: 'Type:', value: 'Epic' }, { trait_type: 'Rarity:', value: 'Common' } ]
]
const result = data.reduce((acc, curr) => {
for (obj of curr) {
const traitType = obj.trait_type
const traitValue = obj.value
const trait = acc.findIndex(o => o[traitType])
if (trait !== -1) {
const value = acc[trait][traitType].findIndex(o => o[traitValue])
if (value !== -1) acc[trait][traitType][value][traitValue] += 1
else acc[trait][traitType].push({ [traitValue]: 1 })
} else acc.push({ [traitType]: [{ [traitValue]: 1 }] })
}
return acc
}, [])
console.log('\nResult = ', JSON.stringify(result, null, 2))
.as-console-wrapper { min-height: 100% }
References:
Upvotes: 0
Reputation: 23372
It might be a good idea to cut up the question in to three parts:
Your input is an array of arrays. The inner layer of arrays does not represent anything in the output data, so we will remove it.
You can "flatten" the array using its flat
method.
This part is mostly about grouping elements of an array. We want to group the objects in your array by their trait_type
property.
Look up "[javascript] groupBy" on this site for more info. I'll summarize it here:
Here's what happens when we group your data by trait_type
:
const groupByProp = (k, xs) => xs.reduce(
(acc, x) => Object.assign(acc, { [x[k]]: (acc[x[k]] || []).concat(x) }),
{}
)
const data = getData().flat();
const step1 = groupByProp("trait_type", data);
console.log(step1)
function getData() { return [[{trait_type:'Type:',value:'Epic'},{trait_type:'Clan:',value:'Vampire'}],[{trait_type:'Rare Accessory:',value:'Rainbow Glass'}],[{trait_type:'Rarity:',value:'Common'}],[{trait_type:'Type:',value:'Common'}],[{trait_type:'Type:',value:'Rare'}],[{trait_type:'Type:',value:'Epic'},{trait_type:'Rarity:',value:'Common'}]]; }
The next step is to create yet another grouping. This time, we'll group each group by value. This is a bit more challenging, because our groups are inside an object. I'll include a small helper here, but you can find more info in questions like this one.
const groupByProp = (k, xs) => xs.reduce((acc, x) => Object.assign(acc, { [x[k]]: (acc[x[k]] || []).concat(x) }),{});
const mapObj = (f, o) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, f(v)]));
const data = getData().flat();
const step1 = groupByProp("trait_type", data);
const step2 = mapObj(xs => groupByProp("value", xs), step1);
console.log(step2)
function getData() { return [[{trait_type:'Type:',value:'Epic'},{trait_type:'Clan:',value:'Vampire'}],[{trait_type:'Rare Accessory:',value:'Rainbow Glass'}],[{trait_type:'Rarity:',value:'Common'}],[{trait_type:'Type:',value:'Common'}],[{trait_type:'Type:',value:'Rare'}],[{trait_type:'Type:',value:'Epic'},{trait_type:'Rarity:',value:'Common'}]]; }
As you can see, we're getting closer to the desired outcome! Instead of the counts, we still have our groups. We can fix this by replacing the arrays with their .length
property:
const groupByProp = (k, xs) => xs.reduce((acc, x) => Object.assign(acc, { [x[k]]: (acc[x[k]] || []).concat(x) }),{});
const mapObj = (f, o) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, f(v)]));
const data = getData().flat();
const step1 = groupByProp("trait_type", data);
const step2 = mapObj(xs => groupByProp("value", xs), step1);
const step3 = mapObj(byType => mapObj(valueGroup => valueGroup.length, byType), step2)
console.log(step3)
function getData() { return [[{trait_type:'Type:',value:'Epic'},{trait_type:'Clan:',value:'Vampire'}],[{trait_type:'Rare Accessory:',value:'Rainbow Glass'}],[{trait_type:'Rarity:',value:'Common'}],[{trait_type:'Type:',value:'Common'}],[{trait_type:'Type:',value:'Rare'}],[{trait_type:'Type:',value:'Epic'},{trait_type:'Rarity:',value:'Common'}]]; }
To be honest, I think the output format above is slightly easier to work with than the one you proposed. But if you do need it, here's how to get to it:
const groupByProp = (k, xs) => xs.reduce((acc, x) => Object.assign(acc, { [x[k]]: (acc[x[k]] || []).concat(x) }),{});
const mapObj = (f, o) => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, f(v)]));
const kvpObj = ([k, v]) => ({ [k]: v });
const kvpGroup = o => Object.entries(o).map(kvpObj);
const data = getData().flat();
const step1 = groupByProp("trait_type", data);
const step2 = mapObj(xs => groupByProp("value", xs), step1);
const step3 = mapObj(byType => mapObj(valueGroup => valueGroup.length, byType), step2)
const output = kvpGroup(mapObj(kvpGroup, step3));
console.log(output);
function getData() { return [[{trait_type:'Type:',value:'Epic'},{trait_type:'Clan:',value:'Vampire'}],[{trait_type:'Rare Accessory:',value:'Rainbow Glass'}],[{trait_type:'Rarity:',value:'Common'}],[{trait_type:'Type:',value:'Common'}],[{trait_type:'Type:',value:'Rare'}],[{trait_type:'Type:',value:'Epic'},{trait_type:'Rarity:',value:'Common'}]]; }
Once you get a feel for the group-by mechanics and all of the moving back and forwards between arrays and objects, you might want to refactor in to something more concise. The snippet below doesn't use the helpers and runs both transformations in one go.
const data = [
[
{ trait_type: 'Type:', value: 'Epic' },
{ trait_type: 'Clan:', value: 'Vampire' },
],
[{ trait_type: 'Rare Accessory:', value: 'Rainbow Glass' }],
[{ trait_type: 'Rarity:', value: 'Common' }],
[{ trait_type: 'Type:', value: 'Common' }],
[{ trait_type: 'Type:', value: 'Rare' }],
[{ trait_type: 'Type:', value: 'Epic' }, { trait_type: 'Rarity:', value: 'Common' }],
]
console.log(
data
.flat()
.reduce(
(acc, x) => Object.assign(
acc,
{ [x.trait_type]: Object.assign(
acc[x.trait_type] || {},
{ [x.value]: (acc[x.trait_type]?.[x.value] ?? 0) + 1 }
) }
),
{}
)
)
Upvotes: 2