Grouping dynamic keys in array of objects - javascript

I have an object that i am trying to transform
var data = {
"A": {"XY1" : 1},
"B": {"XY2": 12},
"C": {"XY3": 10},
"D": {"XY1": 2}
am trying to transform this to
[
{ "XY1": { 1:"A", 2:"D"}},
{ "XY2": { 12:"B"}},
{ "XY3": { 8:"A", 10:"C"}},
]
(we can ignore the ordering of XY1, XY2 etc)
Here is what i have done so far -
var result = Object.keys(data).flatMap(alphabet => {
return Object.keys(data[alphabet]).map(group => {
return Object.assign({}, {
[group]: { [data[alphabet][group]]: alphabet }
})
});
});
console.log(result);
which prints
[
{"XY1":{"1":"A"}},
{"XY3":{"8":"A"}},
{"XY2":{"12":"B"}},
{"XY3":{"10":"C"}},
{"XY1":{"2":"D"}}
]
However, i want it to be grouped by using reduce(chaining), such as -
var result = Object.keys(data).flatMap(alphabet => {
return Object.keys(data[alphabet]).map(group => {
return Object.assign({}, {
[group]: { [data[alphabet][group]]: alphabet }
})
});
}).reduce((obj, item) => {
});
Is this possible ? How do i group by these dynamic keys?
Help much appreciated !

I'd group first using a hashtable:
const hash = {};
for(const [key, obj] of Object.entries(data)) {
for(const [key2, values] of Object.entries(obj)) {
if(!hash[key2]) hash[key2] = {};
for(const value of [values].flat())
hash[key2][value] = key;
}
}
To then get an array you can use Object.entries:
const result = Object.entries(hash).map(([key, value]) => ({ key, value }));
This is not exactly what you wanted, but to be honest I don't see a sense of having an array of objects with just one key each.

Related

How to update an object value in array of objects when the keys are same

I have an Array of objects and one object
const filterArray = [{bestTimeToVisit: 'Before 10am'}, {bestDayToVisit: Monday}]
This values are setting in a reducer and the payload will be like
{bestTimeToVisit: 'After 10am'}
or
{bestDayToVisit: Tuesday}.
So what I need is when I get a payload {bestTimeToVisit: 'After 10am'} and if bestTimeToVisit not in filterList array, then add this value to the filterList array.
And if bestTimeToVisit already in the array with different value, then replace the value of that object with same key
if(filterArray.hasOwnProperty("bestTimeToVisit")) {
filterArray["bestTimeToVisit"] = payload["bestTimeToVisit"];
} else {
filterArray.push({"bestTimeToVisit": payload["bestTimeToVisit"]});
}
I convert the object array into a regular object and then back into an object array. makes things less complicated. I'm making the assumption each object coming back only has one key/value and that order doesnt matter.
const objectArraytoObject = (arr) =>
arr.reduce((acc, item) => {
const key = [Object.keys(item)[0]];
return { ...acc, [key]: item[key] };
}, {});
const newValues = [{ someKey: 'something' }, { bestDayToVisit: 'Tuesday' }];
const filterArray = [
{ bestTimeToVisit: 'Before 10am' },
{ bestDayToVisit: 'Monday' },
];
const newValuesObj = objectArraytoObject(newValues);
const filterObj = objectArraytoObject(filterArray);
const combined = { ...filterObj, ...newValuesObj };
const combinedToArray = Object.keys(combined).map((key) => ({
[key]: combined[key],
}));
console.log(combinedToArray);
Need to iterate over the array and find objects that satisfy for modification or addition if none are found.
function checkReduced(filterrray,valueToCheck="After 10am"){
let isNotFound =true;
for(let timeItem of filterrray) {
if(timeItem.bestTimeToVisit && timeItem.bestTimeToVisit !== valueToCheck) {
timeItem.bestTimeToVisit=valueToCheck;
isNotFound=false;
break;
}
}
if(isNotFound){filterrray.push({bestTimeToVisit:valueToCheck})}
}
const filterArray = [{bestDayToVisit: "Monday"}];
checkReduced(filterArray,"After 9am");//calling the function
const updateOrAdd = (arr, newItem) => {
// get the new item key
const newItemKey = Object.keys(newItem)[0];
// get the object have the same key
const find = arr.find(item => Object.keys(item).includes(newItemKey));
if(find) { // the find object is a reference type
find[newItemKey] = newItem[newItemKey]; // update the value
} else {
arr.push(newItem); // push new item if there is no object have the same key
}
return arr;
}
// tests
updateOrAdd([{ a: 1 }], { b: 2 }) // => [{ a: 1 }, { b: 2 }]
updateOrAdd([{ a: 1 }], { a: 2 }) // => [{ a: 2 }]

Create an array of object properties for each item that has the same key

I'm merging two objects together to create a filter object. However I want to group the merged objects property values where the keys are the same.
So...
[{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}]
becomes
[{category: ['furniture', 'mirrors']}, {availability: 'in_stock'}]
any ideas?
With lodash you merge the entire array to a new object by spreading into _.mergeWith(). The customizer should use empty arrays as default values for the current values, and concat the values. Use _.map() to convert back to an array.
const data = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const result = _.map(
_.mergeWith({}, ...data, (a = [], b = [], key) => a.concat(b)),
(val, key) => ({ [key]: val })
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Using vanilla JS, reduce the array to a Map using the objects' keys as the keys of the Map, with an empty array as the value, and push the objects' values into the arrays. Use Array.from() to convert the Map to an array.
const data = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const result = Array.from(
data.reduce((acc, obj) => {
Object.entries(obj)
.forEach(([key, val]) => {
if(!acc.has(key)) acc.set(key, [])
acc.get(key).push(val)
})
return acc
}, new Map()),
([key, val]) => ({ [key]: val })
)
console.log(result)
You can use reduce like this:
const data = [
{ category: 'furniture' },
{ category: 'mirrors' },
{ availability: 'in_stock' }
];
const result = data.reduce(
(a, x) => {
const key = Object.keys(x)[0]; // find the key of the current object
if (!a.tmp[key]) { // if the current key doesn't exist in the lookup object (tmp) yet ...
a.tmp[key] = []; // create an empty array in the lookup object for the current key
a.result.push({ [key]: a.tmp[key] }); // push the current object to the result
}
a.tmp[key].push(x[key]); // push the current value to the array
return a;
},
{ result: [], tmp: {} },
).result;
console.log(result);
I'm sure there are easier ways to achieve this, but that's the best I can come up with right now.
we can also achieve this by using forEach loop :
const input = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const resultObj = {};
const resultArr = [];
input.forEach((obj) => {
resultObj[Object.keys(obj)[0]] = [];
})
input.forEach((obj) => {
resultObj[Object.keys(obj)[0]].push(obj[Object.keys(obj)[0]]);
resultArr.push(resultObj);
})
console.log([...new Set(resultArr)]);
Another one reduce solution
const arr = [{category: 'furniture', category2: 'furniture2'}, {category: 'mirrors'}, {availability: 'in_stock'}]
const result = Object.values(arr
.flatMap((obj) => Object.entries(obj))
.reduce((acc, [key, value]) => {
acc[key] = acc[key]
? {[key]: [...acc[key][key], value] }
: {[key]: [value] }
return acc;
}, {}));
console.log(result)
.as-console-wrapper{min-height: 100%!important; top: 0}
A generic implementation could achieve a merger of any kind of objects regardless of amount and kind of an(y) object's property names.
Since the result of such an implementation is an object, one needs additional treatment in order to cover the OP's requirement(s).
function mergeAndCollectItemEntries(result, item) {
// return the programmatically aggregated merger/result.
return Object
// get an item's entry array.
.entries(item)
// for each key-value pair ...
.reduce((merger, [key, value]) => {
// ... access and/or create a `key` specific array ...
// ... and push `value` into this array.
(merger[key] ??= []).push(value);
// return the programmatically aggregated merger/result.
return merger;
}, result);
}
const sampleData = [
{ category: 'furniture' },
{ category: 'mirrors' },
{ availability: 'in_stock' },
];
const mergedData = sampleData
.reduce(mergeAndCollectItemEntries, {});
const mergedDataList = Object
.entries(
sampleData
.reduce(mergeAndCollectItemEntries, {})
)
.map(entry => Object.fromEntries([entry]));
//.map(([key, value]) => ({ [key]: value }));
console.log({
sampleData,
mergedData,
mergedDataList,
});
console.log(
Object
.entries([
{ category: 'furniture', foo: 'baz' },
{ category: 'mirrors', bar: 'bizz' },
{ availability: 'in_stock', bar: 'buzz' },
].reduce(
mergeAndCollectItemEntries, {}
)
).map(
([key, value]) => ({ [key]: value })
//entry => Object.fromEntries([entry])
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Another approach here with building an tracking object to merge the values.
Handle the cases of single value keep as string and multiple values as array per the expected output.
const merge = (arr, output = {}) => {
arr.forEach((item) => {
const [[key, val]] = Object.entries(item);
if (key in output) {
output[key] = Array.isArray(output[key])
? output[key].concat(val)
: [output[key]].concat(val);
} else {
output[key] = val;
}
});
return Object.entries(output).map(([key, val]) => ({ [key]: val }));
};
const data = [
{ category: "furniture" },
{ category: "mirrors" },
{ availability: "in_stock" },
];
console.log(merge(data));

Simpler version to filter object?

I have some trouble trying to filter that kind of nested object, i must be wrong but there must be an easier way to do it.
theObject = {
"a": [{"val":"","date":2},{"val":20,"date":2}],
"b": [{"val":"","date":2}],
"c": [{"val":"10","date":1},{"val":20,"date":2},{"val":"30","date":3}]
}
myFilteredObject = {}
What i want to do is to remove from theObject unnecessary data depending on conditions, for example : val != "" or date < 2 plus, i only want the last object. And keep all the good data in myFilteredObject
Example of filtered tab with those two conditions :
myFilteredObject = {
"a": [{"val":20,"date":2}],
"c": [{"val":"30","date":3}]
}
My question : is there a simpler way to write it, here i'm doing two loops of "For... of object.entries"
let theObject = {
"a": [{"val":"","date":2},{"val":20,"date":2}],
"b": [{"val":"","date":2}],
"c": [{"val":"10","date":2},{"val":20,"date":2},{"val":"30","date":1}]
}
let myFilteredObject = {}
const filterFunction = function(){
// i remove all data that do not match my conditions //
for(let [key, value] of Object.entries(tab)){
if(value[value.length-1].val !== "" || value[value.length-1].date < 1){
myFilteredObject[key] = tab[key]
}
// i keep only the last object of each table //
for(let [key, value] of Object.entries(myFilteredObject)){
myFilteredObject[key].splice(0, myFilteredObject[key].length -1)
}
}
}
filterFunction()
console.log("result",myFilteredObject)
Take the initial object's entries, map them to an entry array of just the key and the final item in the array, then filter by whether that final item has a value and a good date:
const theObject = {
"a": [{"val":"","date":2},{"val":20,"date":2}],
"b": [{"val":"","date":2}],
"c": [{"val":"10","date":1},{"val":20,"date":2},{"val":"30","date":3}]
}
const filteredObject = Object.fromEntries(
Object.entries(theObject)
.map(([key, subarr]) => [key, [subarr[subarr.length - 1]]])
.filter(([key, subarr]) => subarr[0].val && subarr[0].date >= 2)
);
console.log(filteredObject);
You could reduce the entries.
const
data = { a: [{ val: "", date: 2 }, { val: 20, date: 2 }], b: [{ val: "", date: 2 }], c: [{ val: "10", date: 1 }, { val: 20, date: 2 }, { val: "30", date: 3 }] },
result = Object.assign({}, ...Object
.entries(data)
.map(([k, v]) => v.reduce((r, o) => {
if (!o.val) return r;
if (o.date > 2) return { [k]: [o] };
(r[k] ??= []).push(o);
return r;
}, {}))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can make a configurable function that receives an object containing information on how to validate each field.
If that object doesn't contain a certain field it will consider it valid by default (the || (() => true)) part)
This way when the rules change, you only change/add items in the validators object, leaving the function body intact. So it means there is less coupling between the code and the shape of the object.
const theObject = {
"a": [{"val":"","date":2},{"val":20,"date":2}],
"b": [{"val":"","date":2}],
"c": [{"val":"10","date":1},{"val":20,"date":2},{"val":"30","date":3}]
}
// put the validation rules here for each field
// if a field has no validator it will be considered valid
const validators = {
val: x => x !== "", // for the `val` field to be valid it has to be !== ""
date: x => x >= 2, // for the `date` field to be valid it has to be >= 2
}
// Function that filters
const filterObject = (obj, validators) =>
Object.entries(obj).reduce((acc, [key, array]) => {
const filteredArray = array.filter((arrayItem) =>
Object.entries(arrayItem).every(([k, v]) =>
(validators[k] || (() => true))(v)));
return { ...acc, ...(filteredArray.length && { [key]: filteredArray }) };
}, {});
// enjoy!
console.log(filterObject(theObject, validators));
Using two for loops is fine. The data is 2 dimensional, so its not bad. Plus, using javascript means that your performance doesnt matter much anyway, unless its awful.
In terms of "simple" below is my style.
Just change "true" to your condition.
One thing I'd like to point out is to use IIFE without polluting namespace, meaning that this filtering function should not directly access variables, but receive them as arguements. This is also good for code reusing and maintaining
((obj,result)=>{
for (i in Object.keys(obj)){
let a = {}
for (item in obj[i]){
if (true){
a = item;
}
}
result[i] = [a]
}
})(theObject, myFilteredObject)

Remove object items from array of objects and group in node js

I have an array of objects as an input.
var val = [{matnr :'0001',type:'Z0001',price:12.3,location:'Afr'},{matnr :'0001',type:'Z0002',price:12.2,location:'US'},
,{matnr :'0002',type:'Z0003',price:11.2,location:'EU'}]
I need to remove location from each object and group by material.
val = [{
matnr:0001
types :[{type:'Z001',price:12.3},{type:'Z001',price:12.2}]
},
{
matnr:0002
types :[{type:'Z003',price:12.3}]
}
I tried to delete an object from an array and did a group by but seems to be not working. Could you please help
val.forEach((values)=>
Object.keys(values).forEach(function (item) {
if (item !='matnr'||item !='type' || item != price){
delete values[item];
};
})
var grouped = _.groupBy(val, function(val) {
return val.matnr;
});
You can use .reduce() with destructuring to remove the location property and group attributes by matnr by creating an object, where each key is matnr, and each value is an accumulation of properties for that given matnr like so:
const arr = [{matnr:"0001",type:"Z0001",price:12.3,location:"Afr"},{matnr:"0001",type:"Z0002",price:12.2,location:"US"},{matnr:"0002",type:"Z0003",price:11.2,location:"EU"}];
const res = Object.values(arr.reduce((acc, {matnr, type, price}) => {
const {types = []} = acc[matnr] || {};
acc[matnr] = {matnr, types: [...types, {type, price}]};
return acc;
}, Object.create(null)));
console.log(res);
.as-console-wrapper {max-height: 100% !important;}
you can do this without using loadash simply by using the higher-order functions
const cleaned = removeFromList(list).key('location')
const grouped = group(list).by('matnr')
function removeFromList(arr) {
return {
key: key => arr.map(item => {
if (!item[key]) return item
delete item[key]
return item
})
}
}
function group(arr) {
return {
by: groupKey => {
const groupsObj = arr.reduce((groups, item) => {
const groupKeyValue = item[groupKey]
if(!groupKeyValue) return groups
if (!groups[groupKeyValue]){
groups[groupKeyValue] = [item]
return groups
}
groups[groupKeyValue] = [...groups[groupKeyValue], item]
return groups
}, {});
return groupsObj
}
}
}
Note We Prefer the object structures for performance and easy access as developers, so on the group by function we return the grouped object with the materials values as keys and the matches as their values.
Like :
{
0001 : [{type:'Z001',price:12.3},{type:'Z001',price:12.2}]
0002 : [{type:'Z003',price:12.3}]
}
example available here on repl.it

convert objects array to griddle readable array

I'm fetching json data with ajax. Then I want to output it in Griddle using griddle-react. The problem is I cannot convert my array to a Griddle readable array.
After the ajax fetch i made a callback function:
function convert(obj) {
console.log(obj);
Object.keys(obj).forEach(function (key) {
let format = JSON.stringify(obj[key]);
console.log(format);
self.setState(() => ({ data: key[format] }));
});
}
The first console.log output looks like this:
{
{
"BTC": {
"opening_price": "9845000",
"closing_price": "9967000",
"min_price": "9814000",
"max_price": "10047000",
"average_price": "9928071.5654",
"units_traded": "7242.04659594",
"volume_1day": "7242.04659594",
"volume_7day": "73491.92898643",
"buy_price": "9967000",
"sell_price": "9968000"
},
}
}
My functions makes it look like this: (second console.log):
{
"opening_price": "9846000",
"closing_price": "9965000",
"min_price": "9814000",
"max_price": "10047000",
"average_price": "9929422.0905",
"units_traded": "7200.46713802",
"volume_1day": "7200.467F13802",
"volume_7day": "73395.33311647",
"buy_price": "9959000",
"sell_price": "9964000"
}
I want it to convert to the following array, basically adding the name item, and thereafter Griddle can read it:
{
"name": "BTC",
"opening_price": "9845000",
"closing_price": "9967000",
"min_price": "9814000",
"max_price": "10047000",
"average_price": "9928071.5654",
"units_traded": "7242.04659594",
"volume_1day": "7242.04659594",
"volume_7day": "73491.92898643",
"buy_price": "9967000",
"sell_price": "9968000"
},
What I'm doing wrong here? I'm sure its pretty close to what I want, but I can't figure it out at this point.
You can use Object.entries to get the keys and values. Use Object.assign to make new objects
var obj = {
"BTC": {"opening_price": "9845000","closing_price": "9967000","min_price": "9814000","max_price": "10047000","average_price": "9928071.5654","units_traded": "7242.04659594","volume_1day": "7242.04659594","volume_7day": "73491.92898643","buy_price": "9967000","sell_price": "9968000"}
}
var newObj = Object.entries(obj).reduce((c, [i, v]) => Object.assign(c, {name: i}, v), {});
console.log(newObj);
If you have several keys, you can use map
var obj = {
"BTC": {"opening_price": "9845000","closing_price": "9967000","min_price": "9814000","max_price": "10047000","average_price": "9928071.5654","units_traded": "7242.04659594","volume_1day": "7242.04659594","volume_7day": "73491.92898643","buy_price": "9967000","sell_price": "9968000"},
"OTH": {"opening_price": "9845000","closing_price": "9967000","min_price": "9814000","max_price": "10047000","average_price": "9928071.5654","units_traded": "7242.04659594","volume_1day": "7242.04659594","volume_7day": "73491.92898643","buy_price": "9967000","sell_price": "9968000"},
}
var newArr = Object.entries(obj).map(([i, v]) => Object.assign({}, {name: i}, v));
console.log(newArr);
Without including date property
var obj = {
"KNC": {"opening_price": "2731","closing_price": "2788","min_price": "2693","max_price": "2849","average_price": "2790.5368","units_traded": "3178032.25814499211673","volume_1day": "3178032.25814499211673","volume_7day": "110687333.315264505902311000","buy_price": "2783","sell_price": "2788"},
"date": "1525269153470"
}
var newObj = Object.entries(obj).reduce((c, [i, v]) => i !== 'date' ? Object.assign(c, {name: i}, v) : c, {});
console.log(newObj);
Can you update your function to have this line in it?
obj[key]["name"] = key
function convert(obj) {
console.log(obj);
Object.keys(obj).forEach(function (key) {
obj[key]["name"] = key;
let format = JSON.stringify(obj[key]);
console.log(format);
//self.setState(() => ({ bithumbData: key[format] }));
});
}
function convert(obj){
var parentKey = Object.keys(obj)[0];//Getting parent first element key
obj = obj[parentKey];
var newObj = {}; //Creating new empty jason object
newObj['name'] = parentKey; //key apply as name element to new jason object
for(var key in obj) //looping each child element
newObj[key] = obj[key]; //child applying to new jason object
return newObj;
}
console.log(JSON.stringify(convert(obj)));

Categories