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
CodePudding user response:
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'}]]; }
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
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'}]]; }
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
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'}]]; }
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
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'}]]; }
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
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 }
) }
),
{}
)
)
<iframe name="sif5" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
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% }
<iframe name="sif6" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
References:
CodePudding user response:
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 );
<iframe name="sif7" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>