It might be a good idea to cut up the question in to three parts:
- Preparing the input to be easier to work with
- The actual logic of counting the type-value combinations
- Transforming the output to your desired format
Preparing the input
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.
Counting the combinations
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:
- Iterate over your list of data
- For every object inside, determine to which group it belongs to
- Store elements belonging to the same group together
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'}]]; }
Transforming to your output data
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'}]]; }
Shortcut:
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 }
) }
),
{}
)
)