Grouping array nested value while comparing 2 objects - javascript

I am working in small react project & I am facing issue in grouping the data. Requirement is to group the id & its feature into a single row if same id is there in before & after object.
Json Data:
{
"before":{
"device": [
{
id:"1234",
price:"10,
features:[
{name:"samsung",price:"10"},
{name:"Apple",price:"20"}
]
},
{id:"2154",
price:"20,
features:[
{name:"samsung",price:"30"},
{name:"Moto",price:"40"}
]
]
},
"after":{
"device": [
{
id:"1234",
price:"50,
features:[
{name:"samsung",price:"20"},
{name:"Lenovo",price:"30"}
]
},
{id:"2158",
price:"40,
features:[
{name:"samsung",price:"30"}
]
]
}
}
Expected grouping to be shown in UI is shared in image.
I tried to get unique ids in one array and lopping through after array and comparing unique array id I am getting unique id to show but issue i am facing while grouping their related feature.
Can anyone please help me to get a best approach to handle this requirement.
Thanks

There are 3 things i'd suggest you:
1.) Please verify the data your'e posting is correct and in proper format, people won't be able to help if the data is incorrect.
2.) The UI display requirement should be simple enough.
Now, if you still want to achieve this requirement i believe the correct JSON and the merged output json will look something like below:
//Correct input data that you have:
var input = {
"before": {
"device": [
{
"id": "1234",
"price": "10",
"features": [
{
"name": "samsung",
"price": "10"
},
{
"name": "Apple",
"price": "20"
}
]
},
{
"id": "2154",
"price": "20",
"features": [
{
"name": "samsung",
"price": "30"
},
{
"name": "Moto",
"price": "40"
}
]
}
]
},
"after": {
"device": [
{
"id": "1234",
"price": "50",
"features": [
{
"name": "samsung",
"price": "20"
},
{
"name": "Lenovo",
"price": "30"
}
]
},
{
"id": "2158",
"price": "40",
"features": [
{
"name": "samsung",
"price": "30"
}
]
}
]
}
};
// Output JSON which you should need to show the desired output.
var output = {
"devices": [
{
"id": 1234,
"feature": [
{
"name": "1234",
"price": {
"before": 10,
"after": 50
}
},
{
"name": "samsung",
"price": {
"before": 10,
"after": 20
}
},
{
"name": "apple",
"price": {
"before": 10,
"after": 0
}
},
{
"name": "lenovo",
"price": {
"before": 0,
"after": 30
}
}
]
}
]
};
3.) Please try to get the desired output from input yourself as this will help you learn a lot of things in between, as suggested by some please use map, filter, forEach for your requirement.
Hope this helps. Thanks!

You could take a nested approach for grouping.
var data = { before: { device: [{ id: "1234", price: "10", features: [{ name: "samsung", price: "10" }, { name: "Apple", price: "20" }] }, { id: "2154", price: "20", features: [{ name: "samsung", price: "30" }, { name: "Moto", price: "40" }] }] }, after: { device: [{ id: "1234", price: "50", features: [{ name: "samsung", price: "20" }, { name: "Lenovo", price: "30" }] }, { id: "2158", price: "40", features: [{ name: "samsung", price: "30" }] }] } },
cols = Object.fromEntries(Object.keys(data).map(k => [k, 0])),
result = Object.values(Object.entries(data).reduce((r, [col, { device }]) => {
device.forEach(({ id, price, features }) => {
r[id] = r[id] || [{ id, ...cols }];
r[id][0][col] = price;
features.forEach(({ name, price }) => {
let temp = r[id].find(q => q.name === name);
if (!temp) r[id].push(temp = { name, ...cols });
temp[col] = price;
});
});
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use lodash library for grouping
https://lodash.com/docs/3.10.1#groupBy

Comparing 2 objects, and output equivalent values
var has = {"before":[{
name: 'Nokia',
os: 'Andriod',
features: {
camera: "200 MPixel Camera",
battery: "24 hours battery backup",
}
}],
"after":[{
name: 'Samsung',
os: 'Andriod',
features: {
camera: "200 MPixel Camera",
battery: "30 hours battery backup",
}
}]
};
function compare(Pro1, Pro2) {
var Val1 = Object.values(Pro1);
var Val2 = Object.values(Pro2);
var equivalent = [];
var keys = Object.keys(Pro1);
keys.forEach(k => {
if (Pro1.hasOwnProperty(k) && Pro2.hasOwnProperty(k)) {
if (typeof Pro1[k] === 'object') {
let recursiveResult = compare(Pro1[k], Pro2[k]);
equivalent.push(...recursiveResult);
} else if (Pro1[k] === Pro2[k]) {
equivalent.push(Pro1[k]);
}
}
});
return equivalent;
}
let equiv = compare(has["before"], has["after"]);
console.log(equiv);

Related

Object transformation using a functional approach

I am reading a simple data set from a data.txt file. I would like to take this data and transform it into a specific object as per my example below. I have managed to get it into a somewhat usable JSON object but this is not ideal. I have included an example of the desired object.
Here is my app.js file:
let output = fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n')
.map((line) => line.split(';'))
.reduce((customers, line) => {
customers.push({
name: line[0],
product: [{
item: line[1],
serial: line[2],
year: line[3]
}]
})
return customers
}, [])
console.log(JSON.stringify(output, null, 2))
This currently the above NodeJs code returns the following array object:
[
{
"name": "Nancy",
"product": [
{
"item": "Macbook Pro",
"serial": "A34D05980FCD4303",
"year": "2019"
}
]
},
{
"name": "Nancy",
"product": [
{
"item": "iPad",
"serial": "O0403X3028423C92",
"year": "2015"
}
]
},
{
"name": "Nancy",
"product": [
{
"item": "iPhone",
"serial": "X3830238S3309230",
"year": "2017"
}
]
},
{
"name": "John",
"product": [
{
"item": "Macbook Pro",
"serial": "X2020J393983H380",
"year": "2013"
}
]
},
{
"name": "John",
"product": [
{
"item": "iPhone",
"serial": "X38320093X032309",
"year": "2015"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iMac",
"serial": "F392D392033X3232",
"year": "2013"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iPad",
"serial": "FE322230D3223S21",
"year": "2011"
}
]
}
]
What I am trying to do is get the below object returned - ideally still following the same functional approach:
[
{
"name": "Nancy",
"product": [
{
"item": "Macbook Pro",
"serial": "A34D05980FCD4303",
"year": "2019"
},
{
"item": "iPad",
"serial": "O0403X3028423C92",
"year": "2015"
},
{
"item": "iPhone",
"serial": "X3830238S3309230",
"year": "2017"
}
]
},
{
"name": "John",
"product": [
{
"item": "Macbook Pro",
"serial": "X2020J393983H380",
"year": "2013"
},
{
"item": "iPhone",
"serial": "X38320093X032309",
"year": "2015"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iMac",
"serial": "F392D392033X3232",
"year": "2013"
},
{
"item": "iPad",
"serial": "FE322230D3223S21",
"year": "2011"
}
]
}
]
Here is my mock data set that lives in data.txt
Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011
Instead of an array you can use Map in reduce as accumulator, use name as key in Map and club value of all keys, finally just get the values Map to get desired output
const data = `Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011`
const final = data.split('\n')
.map(v => v.split(';'))
.reduce((op, [name, item, serial, year]) => {
let obj = { item, serial, year }
if (op.has(name)) {
op.get(name).products.push(obj)
} else{
op.set(name,{name, products:[obj]})
}
return op
}, new Map())
console.log([...final.values()])
Here is a "functional version" that utilizes a Map to find duplicates in O(1):
(map => (
fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n')
.map((line) => line.split(';'))
.forEach(([name, item, serial, year]) =>
map.has(name)
? map.get(name).product.push({ item, serial, year })
: map.set(name, { name, product: [{ item, serial, year }] })
),
[...map.values()]
)(new Map)
But seriously, whats so bad about imperative style?:
const customers = new Map;
const entries = fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n');
for(const entry of entries) {
const [name, item, serial, year] = entry.split(";");
const product = { item, serial, year };
if(customers.has(name)) {
customers.get(name).product.push(product);
} else customers.set(name, { name, product: [product] });
}
const result = [...customers.values()];
You can modify the .reduce function to only add a new item to the array if there isn't one with that name. If there is, just add the product to that item's product array.
const data = `Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011`;
const result = data.trim()
.split('\n')
.map((line) => line.split(';'))
.reduce((customers, line) => {
const product = {
item: line[1],
serial: line[2],
year: line[3]
};
const customer = customers.find(({
name
}) => name === line[0]);
if (customer) {
customer.product.push(product);
} else {
customers.push({
name: line[0],
product: [product]
});
}
return customers
}, []);
console.log(result);

What is the most performant way to convert an Array of Object to an Object with unique keys

I am trying to figure out the most performant Javascript way to convert an array of objects, into an object with unique keys and an array full of objects as the value.
For Example:
const array = [
{ "name": "greg", "year": "2000" },
{ "name": "john", "year": "2002" },
{ "name": "bob", "year": "2005" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" },
];
I would like this converted to:
{
"2000": [
{ "name": "greg", "year": "2000" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" }
],
"2002": [ { "name": "john", "year": "2002" } ],
"2005": [ { "name": "bob", "year": "2005" } ],
}
As of now, this is what I've done so far:
let yearsObj = {};
for (let i=0; i<array.length; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
you can use a more elegant way to do it by using array's reduce function
// # impl
const group = key => array =>
array.reduce(
(objectsByKeyValue, obj) => ({
...objectsByKeyValue,
[obj[key]]: (objectsByKeyValue[obj[key]] || []).concat(obj)
}),
{}
);
// # usage
console.log(
JSON.stringify({
byYear: group(array),
}, null, 1)
);
// output
VM278:1 {
"carsByBrand": {
"2000": [
{
"name": "greg",
"year": "2000"
},
{
"name": "ned",
"year": "2000"
},
{
"name": "pam",
"year": "2000"
}
],
"2002": [
{
"name": "john",
"year": "2002"
}
],
"2005": [
{
"name": "bob",
"year": "2005"
}
]
}
}
It could be as simple as that Object.fromEntries(array.map(obj => [obj.year,obj])) even it is not exactly what you need, but talking about performance it is way slower than all proposed, so i'm giving it as an bad example of showing how the short statement is not always the fastest.
Your way seems to be the fastest taking about performance.
Run the snippet below to see the actual timing.
// common
let array = [
{ "name": "greg", "year": "2000" },
{ "name": "john", "year": "2002" },
{ "name": "bob", "year": "2005" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" },
];
// simple as a statement way
console.time();
console.log(Object.fromEntries(array.map(obj => [obj.year,obj])));
console.timeEnd();
// using .reduce way
console.time();
const result = array.reduce((prev, curr) => {
const { year } = curr;
if (prev[year]) {
prev[year].push(curr);
} else {
prev[year] = [curr];
}
return prev;
}, {});
console.log(result);
console.timeEnd();
// your way
console.time();
let yearsObj = {};
for (let i=0; i<array.length; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
console.log(yearsObj);
console.timeEnd();
A for loop (imperative style) like you have is likely to be the fastest in most situations. However, in this case you are not likely to see much of a difference. One thing you could do to improve the code in your example is to get the array length before the for loop and assign it to the variable, so that it's not calculated every iteration of the loop.
const yearsObj = {};
const arrayLength = array.length; // Only calculate array length once
for (let i=0; i<arrayLength; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
In this situation, my preference would be to use Array.reduce(). It is more readable and the performance difference will be negligible.
const arr = [
{ name: 'greg', year: '2000' },
{ name: 'john', year: '2002' },
{ name: 'bob', year: '2005' },
{ name: 'ned', year: '2000' },
{ name: 'pam', year: '2000' },
];
const result = arr.reduce((prev, curr) => {
const { year } = curr;
if (prev[year]) {
prev[year].push(curr);
} else {
prev[year] = [curr];
}
return prev;
}, {});
/* Result:
{ '2000':
[ { name: 'greg', year: '2000' },
{ name: 'ned', year: '2000' },
{ name: 'pam', year: '2000' } ],
'2002': [ { name: 'john', year: '2002' } ],
'2005': [ { name: 'bob', year: '2005' } ] }
*/

javascript map object multidimension

i have this data structure:
tree = [
{
"name": "Men Section",
"categories": [
{
"name": "Clothings",
"Subcategories": [
{
"name": "Jackets",
"products": [
{
"name": "jacket 01",
"price": 100
},
{
"name": "jacket 02",
"price": 140
},
// ..and so on
]
]
},
// ..and so on
]
} // ..and so on
]
how can i add new property in products item isSelected: false in javascript (ES5 or ES6 is fine) so the object will be
{
"name": "jacket 01",
"price": 100,
"isSelected": false
}
?
It could be useful, it works for me
tree.forEach(base => {
base.categories.forEach(categories => {
categories.Subcategories.forEach(subCategory =>{
subCategory.products.forEach(product => product.isSelected = false)
})
});
});
so yeah, i should traverse deep to the object with nested foreach. i came up with this:
tree.categories.forEach(function (category) {
category.subcategories.forEach(function (subcategory) {
subcategory.products.forEach(function (product) {
product.isSelected = false;
})
})
})
thanks for the enlightment

How to move element in nested array

i wanted to move element in nested array. so, here this my data:
let products = [
{
"product_name": "A",
"_id": "5ace995c14a759325776aab1",
"transactions": [
{
"_id": "5ad3a274ac827c165a510f99",
"qty": 100,
"price": 2000
},
{
"_id": "5ad3a274ac827c165a510f99",
"qty": 80,
"price": 1500
},
]
},
{
"product_name": "B",
"_id": "5ace995914a759325776aab0",
"transactions": [
{
"_id": "5ad3a274ac827c165a510f9b",
"qty": 80,
"price": 1500
}
],
}
]
The output that i expected:
[
{
"_id": "5ad3a274ac827c165a510f99",
"qty": 100,
"price": 2000,
"product_name": "A",
},
{
"_id": "5ad3a274ac827c165a510f99",
"qty": 80,
"price": 1500,
"product_name": "A",
},
{
"_id": "5ad3a274ac827c165a510f9b",
"qty": 80,
"price": 1500,
"product_name": "B",
}
]
then, my solve code:
function move() {
var result = []
for (product of products) {
for (transaction of product.transactions) {
transaction.product_name = product.product_name
result.push(transaction)
}
}
return result
}
product = move()
Is there any effective way to create the output, maybe with array map or anything else? Thank you.
You could flat the transactions with Array#reduce and using Object.assign for adding product_name.
Also used:
destructuring assignment for the properties and
short hand properties for taking a variable as property with the name as key.
var products = [{ product_name: "A", _id: "5ace995c14a759325776aab1", transactions: [{ _id: "5ad3a274ac827c165a510f99", qty: 100, price: 2000 }, { _id: "5ad3a274ac827c165a510f99", qty: 80, price: 1500 }] }, { product_name: "B", _id: "5ace995914a759325776aab0", transactions: [{ _id: "5ad3a274ac827c165a510f9b", qty: 80, price: 1500 }] }],
result = products.reduce((r, { transactions, product_name }) =>
r.concat(transactions.map(t => Object.assign({}, t, { product_name }))),
[]
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can reduce and map the transactions to add the product name
let result = products.reduce((c,v)=>{ //Loop the array using reduce
let transactions = v.transactions.map(o=>{ //Loop thru each transactions using map
return Object.assign(o,{"product_name":v.product_name}); //Clone the transaction and add the property product_name
});
return c.concat(transactions); //Merge the current array and the transactions
},[]);
Here is a snippet:
//Your array
let products=[{"product_name":"A","_id":"5ace995c14a759325776aab1","transactions":[{"_id":"5ad3a274ac827c165a510f99","qty":100,"price":2000},{"_id":"5ad3a274ac827c165a510f99","qty":80,"price":1500},]},{"product_name":"B","_id":"5ace995914a759325776aab0","transactions":[{"_id":"5ad3a274ac827c165a510f9b","qty":80,"price":1500}],}]
//The short version
let result = products.reduce((c, v) => c.concat(v.transactions.map(o =>Object.assign(o, {"product_name": v.product_name}))), []);
console.log(result);
Just using js methods you can have your desired output
const products = [
{
"product_name": "A",
"_id": "5ace995c14a759325776aab1",
"transactions": [
{
"_id": "5ad3a274ac827c165a510f99",
"qty": 100,
"price": 2000
},
{
"_id": "5ad3a274ac827c165a510f99",
"qty": 80,
"price": 1500
},
]
},
{
"product_name": "B",
"_id": "5ace995914a759325776aab0",
"transactions": [
{
"_id": "5ad3a274ac827c165a510f9b",
"qty": 80,
"price": 1500
}
],
}
]
let output =[];
products.forEach(elm => elm.transactions.forEach(transaction => {
transaction.product_name = elm.product_name;
output.push(transaction)}));
console.log(output);

Create new array with another structure in Javascript

I want to create a new array based on an original array but with merged data.
Every name key need to have merged date+time (format: YYYY-MM-DD HH:MM) with merged scores. All unique datetimes need to be available as key for each name.
ARRAY ORIGINAL:
"data": [{
"name": "A",
"history": [{
"created": "2017-05-16 00:00:00",
"score": "1"
},
{
"created": "2017-05-16 00:01:10",
"score": "1"
},
{
"created": "2017-05-16 00:01:30",
"score": "1"
}
]
},
{
"name": "B",
"history": [{
"created": "2017-05-16 00:01:00",
"score": "1"
}]
}
]
ARRAY THAT I WANT:
{
[A]: {
"2017-05-16 00:00": 1,
"2017-05-16 00:01": 2
},
[B]: {
"2017-05-16 00:00": 0,
"2017-05-16 00:01": 1
}
}
I hope you guys can help me out. I can't even think of an efficiƫnt way to do this, unfortunately. I tried to solve this issue with 5 foreach statements with no luck :(
You could use two arrays for names and times as closure and generate for all names and times a property with zero value.
var data = { data: [{ name: "A", history: [{ created: "2017-05-16 00:00:00", score: "1" }, { created: "2017-05-16 00:01:10", score: "1" }, { created: "2017-05-16 00:01:30", score: "1" }] }, { name: "B", history: [{ created: "2017-05-16 00:01:00", score: "1" }] }] },
result = data.data.reduce(function (names, times) {
return function (r, a) {
if (!r[a.name]) {
r[a.name] = {};
times.forEach(function (time) {
r[a.name][time] = 0;
});
names.push(a.name);
}
a.history.forEach(function (o) {
var time = o.created.slice(0, 16);
if (times.indexOf(time) === -1) {
names.forEach(function (name) {
r[name][time] = 0;
});
times.push(time);
}
r[a.name][time] += +o.score;
});
return r;
};
}([], []), {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You have to create an object not an array, As arrays cannot have a key-value pair in javascript. There is no associative array concept in javascript. You have to use objects in javascript for that.
Here is how you can do what you are trying to achieve using objects.
value = {
"data": [{
"name": "A",
"history": [{
"created": "2017-05-16 00:00:00",
"score": "1"
},
{
"created": "2017-05-16 00:01:10",
"score": "1"
},
{
"created": "2017-05-16 00:01:30",
"score": "1"
}
]},
{
"name": "B",
"history": [{
"created": "2017-05-16 00:01:00",
"score": "1"
}]
}]
};
var result ={};
value.data.forEach(function(v){
var score = {};
for(var i=0;i<v.history.length;i++){
score[v.history[i].created] = v.history[i].score;
}
result[v.name] = score;
});
console.log(result);
Now you can access data as result.A or result[A] and result.B or result[B]
SNIPPET
value = {
"data": [{
"name": "A",
"history": [{
"created": "2017-05-16 00:00:00",
"score": "1"
},
{
"created": "2017-05-16 00:01:10",
"score": "1"
},
{
"created": "2017-05-16 00:01:30",
"score": "1"
}
]
},
{
"name": "B",
"history": [{
"created": "2017-05-16 00:01:00",
"score": "1"
}]
}
]
};
var result = {};
value.data.forEach(function(v) {
var score = {};
for (var i = 0; i < v.history.length; i++) {
score[v.history[i].created] = v.history[i].score;
}
result[v.name] = score;
});
console.log(result);

Categories