re-organizing array of objects - javascript

So I have an array of objects that contain information for different activities in different projects.
It looks something like this
const input = [
{
Project: 1,
ID: "1-1",
},
{
Project: 1,
ID: "1-2",
},
{
Project: 2,
ID: "2-1",
},
];
From this, I would like to go to this
output = [{
Project: 1,
ID1: 1 - 1,
ID2: 1 - 2,
},
{
Project: 1,
ID1: 2 - 1,
},
];
Here's what I have so far:
let currentProject = ''
let output = []
for (const e of input) {
let counter
let outputObj = {}
if (currentProject !== e.Project) {
output.push(outputObj)
counter = 1
outputObj = {}
outputObj.projectNum = e.Project
currentProject = e.Project
}
if (currentProject == e.Project) {
outputObj['ID' + counter] = e.ID
counter++
}
}
here's what I'm getting back:
output = [{
Project: 1,
ID1: 1 - 1
},
{
Project: 1,
ID1: 2 - 1
}
]
I'm not sure what the issue is, tried several times to fix it.
Could someone please help me get over the edge?
Any help will be greatly appreciated.

You can achieve this using reduce, Object.keys
const input = [{
Project: 1,
ID: "1-1",
},
{
Project: 1,
ID: "1-2",
},
{
Project: 2,
ID: "2-1",
},
];
const result = input.reduce((acc, curr) => {
const { Project, ID } = curr;
const obj = acc.find((el) => el.Project === Project);
if (obj) {
const length = Object.keys(obj).length;
obj[`ID${length}`] = ID;
} else {
acc.push({ Project, [`ID${1}`]: ID });
}
return acc;
}, []);
console.log(result);

You can try this.
const input=[{Project:1,ID:"1-1",},{Project:1,ID:"1-2",},{Project:2,ID:"2-1"}];
let temp = {};
input.map(v=>(temp[v.Project] ??= []).push(v.ID));
let output = Object.keys(temp).map(k=>{
let json = {Project:k};
temp[k].map((v,k)=>json['ID'+(Number(k+1))]=v);
return json;
});
console.log(output);
you will get the result.
[
{ Project: '1', ID1: '1-1', ID2: '1-2' },
{ Project: '2', ID1: '2-1' }
]

The way you intended to implement this assumes every project from same id comes sequentially grouped.
While #decpk answer deals with with a linear search, for performance reasons I would rather first use a dictionary and then convert to an array, AND also keep track of id quantities using a field n.
const input = [
{
Project: 1,
ID: "1-1",
},
{
Project: 1,
ID: "1-2",
},
{
Project: 2,
ID: "2-1",
},
];
const projects = {}
for (const e of input) {
let pid = e.Project
let project = projects[pid]
//First time seeing this project
if (!project) {
projects[pid] = { Project: pid, n: 1, ID1: e.ID }
}
//insert more ID
else {
project.n += 1
project[`ID${project.n}`] = e.ID
}
}
//And now converting the object to array, removing the 'n' field
const output = Object.keys(projects).map(pid => {
const obj = projects[pid]
delete obj.n
obj.Project = pid
return obj
})

You can try this way - O(n) time complexity
Using reduce to aggregate data.
Using logical nullish assignment only assigns if acc[Project] is nullish (null or undefined).
Define each additional key-value pair like:
const number = Object.keys(acc[Project]).length; // Define key format by number of existing property.
const key = `ID${number}`;
instead of using count variable.
const input=[{Project:1,ID:"1-1",},{Project:1,ID:"1-2",},{Project:2,ID:"2-1",}];
const output = input.reduce((acc, {Project, ID}) =>
{
acc[Project] ??= {Project}; // Get exist object or create new one
const number = Object.keys(acc[Project]).length; // Define key format by number of existing property.
const key = `ID${number}`;
acc[Project][key] = ID;
return acc;
}, {});
console.log(Object.values(output));
Output:
[
{
"Project": 1,
"ID1": "1-1",
"ID2": "1-2"
},
{
"Project": 2,
"ID1": "2-1"
}
]

Related

Create an array of objects based on an object if one or more properties have multiple values differentiated by a comma

i'm trying to duplicate objects based on two properties that have multiple values differentiated by a comma.
For example:
I have an object
const obj = {
id: 1
date: "2021"
tst1: "111, 222"
tst2: "AAA, BBB"
}
And I would like the result to be an array of 2 objects in this case (because there are 2 values in tst1 OR tst2, these 2 properties will always have the same nr of values differentiated by a comma)
[{
id: 1,
date: "2021",
tst1: "111",
tst2: "AAA",
},
{
id: 1,
date: "2021",
tst1: "222",
tst2: "BBB",
}]
What I tried is this:
I created a temporary object
const tempObject = {
id: obj.id,
date: obj.date,
}
And then I would split and map the property that has multiple values, like this:
cont newObj = obj.tst1.split(",").map(function(value) {
let finalObj = {}
return finalObj = {
id: tempObject.id,
date: tempObject.date,
tst1: value,
})
And now, the newObj is an array of objects and each object contains a value of tst1.
The problem is I still have to do the same for the tst2...
And I was wondering if there is a simpler method to do this...
Thank you!
Here is an example that accepts an array of duplicate keys to differentiate. It first maps them to arrays of entries by splitting on ',' and then trimming the entries, then zips them by index to create sub-arrays of each specified property, finally it returns a result of the original object spread against an Object.fromEntries of the zipped properties.
const mapDuplicateProps = (obj, props) => {
const splitProps = props.map((p) =>
obj[p].split(',').map((s) => [p, s.trim()])
);
// [ [[ 'tst1', '111' ], [ 'tst1', '222' ]], [[ 'tst2', 'AAA' ], [ 'tst2', 'BBB' ]] ]
const dupeEntries = splitProps[0].map((_, i) => splitProps.map((p) => p[i]));
// [ [[ 'tst1', '111' ], [ 'tst2', 'AAA' ]], [[ 'tst1', '222' ], [ 'tst2', 'BBB' ]] ]
return dupeEntries.map((d) => ({ ...obj, ...Object.fromEntries(d) }));
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(mapDuplicateProps(obj, ['tst1', 'tst2']));
Not sure if that's what you're searching for, but I tried making a more general use of what you try to do:
const duplicateProperties = obj => {
const properties = Object.entries(obj);
let acc = [{}];
properties.forEach(([key, value]) => {
if (typeof value === 'string' && value.includes(',')) {
const values = value.split(',');
values.forEach((v, i) => {
if (!acc[i]) {
acc[i] = {};
}
acc[i][key] = v.trim();
});
} else {
acc.forEach(o => o[key] = value);
}
});
return acc;
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(duplicateProperties(obj));
You could start by determining the length of the result using Math.max(), String.split() etc.
Then you'd create an Array using Array.from(), returning the correct object for each value of the output index.
const obj = {
id: 1,
date: "2021",
tst1: "111, 222",
tst2: "AAA, BBB",
}
// Determine the length of our output array...
const length = Math.max(...Object.values(obj).map(s => (s + '').split(',').length))
// Map the object using the relevant index...
const result = Array.from({ length }, (_, idx) => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
const a = (value + '').split(/,\s*/);
return [key, a.length > 1 ? a[idx] : value ]
}))
})
console.log(result)
.as-console-wrapper { max-height: 100% !important; }

How to dedupe an array of objects by a key value pair?

// This is a large array of objects, e.g.:
let totalArray = [
{"id":"rec01dTDP9T4ZtHL4","fields":
{"user_id":170180717,"user_name":"abcdefg","event_id":516575,
}]
let uniqueArray = [];
let dupeArray = [];
let itemIndex = 0
totalArray.forEach(x => {
if(!uniqueArray.some(y => JSON.stringify(y) === JSON.stringify(x))){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
node.warn(totalArray);
node.warn(uniqueArray);
node.warn(dupeArray);
return msg;
I need my code to identify duplicates in the array by a key value of user_id within the objects in the array. Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this? I am struggling to figure out how to path the for each loop to identify the dupe based on the key value instead of the entire object.
Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this?
Don’t compare the JSON representation of the whole objects then, but only their user_id property specifically.
totalArray.forEach(x => {
if(!uniqueArray.some(y => y.fields.user_id === x.fields.user_id)){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
You could take a Set and push to either uniques or duplicates.
var array = [
{ id: 1, data: 0 },
{ id: 2, data: 1 },
{ id: 2, data: 2 },
{ id: 3, data: 3 },
{ id: 3, data: 4 },
{ id: 3, data: 5 },
],
uniques = [],
duplicates = [];
array.forEach(
(s => o => s.has(o.id) ? duplicates.push(o) : (s.add(o.id), uniques.push(o)))
(new Set)
);
console.log(uniques);
console.log(duplicates);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way is to keep a list of ids you found so far and act accordingly:
totalArray = [
{ id: 1, val: 10 },
{ id: 2, val: 20 },
{ id: 3, val: 30 },
{ id: 2, val: 15 },
{ id: 1, val: 50 }
]
const uniqueArray = []
const dupeArray = []
const ids = {}
totalArray.forEach( x => {
if (ids[x.id]) {
dupeArray.push(x)
} else {
uniqueArray.push(x)
ids[x.id] = true
}
})
for (const obj of uniqueArray) console.log("unique:",JSON.stringify(obj))
for (const obj of dupeArray) console.log("dupes: ",JSON.stringify(obj))

how to count duplicate values object to be a value of object

how to count the value of object in new object values
lets say that i have json like this :
let data = [{
no: 3,
name: 'drink'
},
{
no: 90,
name: 'eat'
},
{
no: 20,
name: 'swim'
}
];
if i have the user pick no in arrays : [3,3,3,3,3,3,3,3,3,3,3,90,20,20,20,20]
so the output should be an array
[
{
num: 3,
total: 11
},
{
num: 90,
total: 1
},
{
num:20,
total: 4
}
];
I would like to know how to do this with a for/of loop
Here is the code I've attempted:
let obj = [];
for (i of arr){
for (j of data){
let innerObj={};
innerObj.num = i
obj.push(innerObj)
}
}
const data = [{"no":3,"name":"drink"},{"no":90,"name":"eat"},{"no":20,"name":"swim"}];
const arr = [3,3,3,3,3,3,3,3,3,3,3,20,20,20,20,80,80];
const lookup = {};
// Loop over the duplicate array and create an
// object that contains the totals
for (let el of arr) {
// If the key doesn't exist set it to zero,
// otherwise add 1 to it
lookup[el] = (lookup[el] || 0) + 1;
}
const out = [];
// Then loop over the data updating the objects
// with the totals found in the lookup object
for (let obj of data) {
lookup[obj.no] && out.push({
no: obj.no,
total: lookup[obj.no]
});
}
document.querySelector('#lookup').textContent = JSON.stringify(lookup, null, 2);
document.querySelector('#out').textContent = JSON.stringify(out, null, 2);
<h3>Lookup output</h3>
<pre id="lookup"></pre>
<h3>Main output</h3>
<pre id="out"></pre>
Perhaps something like this? You can map the existing data array and attach filtered array counts to each array object.
let data = [
{
no: 3,
name: 'drink'
},
{
no:90,
name: 'eat'
},
{
no:20,
name: 'swim'
}
]
const test = [3,3,3,3,3,3,3,3,3,3,3,90,20,20,20,20]
const result = data.map((item) => {
return {
num: item.no,
total: test.filter(i => i === item.no).length // filters number array and then checks length
}
})
You can check next approach using a single for/of loop. But first I have to create a Set with valid ids, so I can discard noise data from the test array:
const data = [
{no: 3, name: 'drink'},
{no: 90, name: 'eat'},
{no: 20, name: 'swim'}
];
const userArr = [3,3,3,3,3,3,3,3,7,7,9,9,3,3,3,90,20,20,20,20];
let ids = new Set(data.map(x => x.no));
let newArr = [];
for (i of userArr)
{
let found = newArr.findIndex(x => x.num === i)
if (found >= 0)
newArr[found].total += 1;
else
ids.has(i) && newArr.push({num: i, total: 1});
}
console.log(newArr);

Destructuring array of objects in es6

In es6, how can i simplify the following lines using destructuring?:
const array0 = someArray[0].data;
const array1 = someArray[1].data;
const array2 = someArray[2].data;
Whether using destructuring would actually be a simplification is debatable but this is how it can be done:
const [
{ data: array0 },
{ data: array1 },
{ data: array2 }
] = someArray
Live Example:
const someArray = [
{ data: 1 },
{ data: 2 },
{ data: 3 }
];
const [
{ data: array0 },
{ data: array1 },
{ data: array2 }
] = someArray
console.log(array0, array1, array2);
What is happening is that you're first extracting each object from someArray then destructuring each object by extracting the data property and renaming it:
// these 2 destructuring steps
const [ obj1, obj2, obj3 ] = someArray // step 1
const { data: array0 } = obj1 // step 2
const { data: array1 } = obj2 // step 2
const { data: array2 } = obj3 // step 2
// written together give
const [
{ data: array0 },
{ data: array1 },
{ data: array2 }
] = someArray
Maybe combine destructuring with mapping for (potentially) more readable code:
const [array0, array1, array2] = someArray.map(item => item.data)
Live Example:
const someArray = [
{ data: 1 },
{ data: 2 },
{ data: 3 }
];
const [array0, array1, array2] = someArray.map(item => item.data)
console.log(array0, array1, array2);
I believe what you actually want is
const array = someArray.map(x => x.data)
If you really want three variables (Hint: you shouldn't), you can combine that mapping with destructuring:
const [array0, array1, array2] = someArray.map(x => x.data)
If you want to do with this pure JS then follow this code snippet. It will help you.
let myArray = [
{
"_id": "1",
"subdata": [
{
"subid": "11",
"name": "A"
},
{
"subid": "12",
"name": "B"
}
]
},
{
"_id": "2",
"subdata": [
{
"subid": "12",
"name": "B"
},
{
"subid": "33",
"name": "E"
}
]
}
]
const array = myArray.map(x => x.subdata).flat(1)
const isExist = (key,value, a) => {
return a.find(item => item[key] == value)
}
let a = array.reduce((acc, curr) => {
if(!isExist('subid', curr.subid, acc)) {
acc.push(curr)
}
return acc
}, [])
console.log(a)
const myInfo = someArray.map((item) => {
const {itemval1, itemval2} = item;
return(
//return data how you want it eg:
<p>{itemval1}</p>
<p>{itemval2}</p>)
})
This is how I did it in react, correct me if m wrong, I'm still new to this world
#Daniel, I presume you were looking to destructure a nested Object in an array of Objects. Following #nem035 was able to extract the nested Object's property using his pattern.
What is happening is that you're first extracting each object from addresses array then destructuring each object by extracting its properties and renaming it including the nested Object:
addresses = [
{
locality:"Sarjapura, Bangalore",
coordinates:{latitude:"12.901160", longitude:"77.711680"}
},
{
locality:"Vadakara, Kozhikode",
coordinates:{latitude:"11.588980", longitude:"75.596450"}
}
]
const [
{locality:loc1, coordinates:{latitude:lat1, longitude:ltd1}},
{locality:loc2, coordinates:{latitude:lat2, longitude:ltd2}}
] = addresses
console.log(`Latitude of Vadakara :: ${lat2}`)

Nested grouping with javascript (ES5)

I have an array of objects as following :
[
{"id":1,"lib":"A","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
{"id":2,"lib":"B","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
...
{"id":110,"lib":"XXX","categoryID":90,"categoryTitle":"Cat90","moduleID":"4","moduleTitle":"Module 4"}
]
I want to group this array by (moduleID,moduleTitle) and then by (categoryID,categoryTitle).
This is what I tried :
function groupBy(data, id, text) {
return data.reduce(function (rv, x) {
var el = rv.find(function(r){
return r && r.id === x[id];
});
if (el) {
el.children.push(x);
} else {
rv.push({ id: x[id], text: x[text], children: [x] });
}
return rv;
}, []);
}
var result = groupBy(response, "moduleID", "moduleTitle");
result.forEach(function(el){
el.children = groupBy(el.children, "categoryID", "categoryTitle");
});
The above code is working as expected, but as you can see, after the first grouping I had to iterate again over the array which was grouped by the moduleId in order to group by the categoryId.
How can I modify this code so I can only call groupBy function once on the array ?
Edit:
Sorry this might be late, but I want this done by using ES5, no Shim and no Polyfill too.
Here's one possible (although may be a bit advanced) approach:
class DefaultMap extends Map {
constructor(factory, iter) {
super(iter || []);
this.factory = factory;
}
get(key) {
if (!this.has(key))
this.set(key, this.factory());
return super.get(key);
}
}
Basically, it's the a Map that invokes a factory function when a value is missing. Now, the funny part:
let grouper = new DefaultMap(() => new DefaultMap(Array));
for (let item of yourArray) {
let firstKey = item.whatever;
let secondKey = item.somethingElse;
grouper.get(firstKey).get(secondKey).push(item);
}
For each firstKey this creates a Map inside grouper, and the values of those maps are arrays grouped by the second key.
A more interesting part of your question is that you're using compound keys, which is quite tricky in JS, since it provides (almost) no immutable data structures. Consider:
items = [
{a: 'one', b: 1},
{a: 'one', b: 1},
{a: 'one', b: 2},
{a: 'two', b: 2},
]
let grouper = new DefaultMap(Array);
for (let x of items) {
let key = [x.a, x.b]; // wrong!
grouper.get(key).push(x);
}
So, we're naively grouping objects by a compound key and expecting to see two objects under ['one', 1] in our grouper (which is one level for the sake of the example). Of course, that won't work, because each key is a freshly created array and all of them are different for Map or any other keyed storage.
One possible solution is to create an immutable structure for each key. An obvious choice would be to use Symbol, e.g.
let tuple = (...args) => Symbol.for(JSON.stringify(args))
and then
for (let x of items) {
let key = tuple(x.a, x.b); // works
grouper.get(key).push(x);
}
You could extend your function by using an array for the grouping id/names.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
groups.reduce(function (level, key) {
var el;
level.some(function (r) {
if (r && r.id === x[key[0]]) {
el = r;
return true;
}
});
if (!el) {
el = { id: x[key[0]], text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv).push({ id: x.id, text: x.lib });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }],
result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version with path as id.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
var path = [];
var last = groups.reduce(function (level, key, i) {
path.length = i;
path[i] = key[0].slice(0, -2).toUpperCase() + ':' + x[key[0]];
var id = path.join(';'),
el = level.find(function (r) {
return r && r.id === id;
});
if (!el) {
el = { id: path.join(';'), text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv);
last.push({ id: path.concat('NODE:' + x.id).join(';') });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }];
var result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do it like this:
const exit = Symbol("exit");
function groupBy(arr, ...props){
const root = {};
for(const el of arr){
const obj = props.map(key => el[key])
.reduce((obj, key) => obj[key] || (obj[key] = {}), root);
(obj[exit] || (obj[exit] = [])).push(el);
}
}
So you can access it like:
const grouped = groupBy(response, "moduleID", "moduleTitle");
console.log( grouped[2]["workflow"][exit] );
You might leave away that exit symbol, but it feels a bit wrong to mix a nested tree with arrays.

Categories