How to merge two object with different properties - javascript

I have something like this :
let user = [
{
name: "step-one",
values: {companyName: "Name", address: "company address"}
},
{
name: "step-two",
values: {name: "User", mobile: 0123}
},
{
name: "step-three",
values: [
{file: "companyLogo", values: {active: true, fileName: "some name"}},
{file: "avatar", values: {active: true, fileName: "file name"}}
]
}
]
I want to get only values and put them into a new object. Thus, something like :
let wantedResult = {
companyName: "Name",
address: "company address",
name: "User",
mobile: 0123,
files: [
{file: "companyLogo", values: {active: false, fileName: "some name"}},
{file: "avatar", values: {active: false, fileName: "file name"}}
]
};
Any advice how I can do that?

You can try this!
let user = [{
name: "step-one",
values: {
companyName: "Name",
address: "company address"
}
}, {
name: "step-two",
values: {
name: "User",
mobile: 0123
}
}, {
name: "step-three",
values: [{
file: "companyLogo",
values: {
active: true,
fileName: "some name"
}
}, {
file: "avatar",
values: {
active: true,
fileName: "file name"
}
}]
}]
var wantedResult = Object.assign({}, user[0].values, user[1].values, {files: user[2].values})
console.log(wantedResult)

It is a bit hacky because of the files array, but i would do it like that:
var wantedResult = user.reduce((result, step) => {
var values = Array.isArray(step.values) ? { files: step.values } : step.values;
return Object.assign({}, result, values)
}, {});
Of course it'll work only for that kind of structure you provided. If you have more objects that have an array in the 'values' property, you'll need to rethink the approach.

Step 3 is inconsistent with the others. If possible, make it consistent to have: values: files: [ { file1_data }, { file2_data } ].
After fixing the inconsistency, you can iterate through each of the steps and add the new properties to the result.
let wantedResult = user.reduce(function(result, spec) {
Object.keys(spec.values).forEach(function(key) {
result[key] = spec.values[key];
});
return result;
}, {});
If not possible to change step 3, you can make it a bit less clean:
let wantedResult = user.reduce(function(result, spec) {
if (spec.name === "step-three") {
result.files = spec.values;
}
else {
Object.keys(spec.values).forEach(function(key) {
result[key] = spec.values[key];
});
}
return result;
}, {});

Related

Combine duplicate tokens inside huge JSON file into nested array of objects using React

I looked at several of the suggested solutions but none seemed to rise to this confounding data formatting challenge.
I have a huge JSON file (over 100k rows) and massive duplicates of data all as top level objects. Here's an example:
[
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326B",
"chipset":"Mediatek MT6853V/NZA",
"date":"2022-01-01",
"fw_id":"A326BXXS4AVA1",
"android":"R(Android 11)",
"known_passcode":false,
"afu":false,
"bfu":false,
"bruteforce":false
},
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326U",
"chipset":"Mediatek MT6853V/NZA",
"date":"2021-03-01",
"fw_id":"A326USQU1AUD4",
"android":"R(Android 11)",
"known_passcode":true,
"afu":false,
"bfu":true,
"bruteforce":true
},
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326U1",
"chipset":"Mediatek MT6853V/NZA",
"date":"2021-09-01",
"fw_id":"A326U1UEU5AUJ2",
"android":"R(Android 11)",
"known_passcode":true,
"afu":false,
"bfu":true,
"bruteforce":true
},
{
"manufacturer":"LGE",
"device":"LG K31",
"model":"LGL355DL",
"chipset":"Mediatek MT6762",
"date":"unknown",
"fw_id":"L355DL10l",
"android":"unknown",
"known_passcode":false,
"afu":false,
"bfu":false,
"bruteforce":false
}
]
This needs to be organized so that data points like manufacturer, device, model are not duplicated hundreds of times.
Btw, here's a JSFiddle to play with:
https://jsfiddle.net/xpancom/Lq7duahv/
Ideally, the JSON format would be the following:
[
{
"manufacturers": [
{
"manufacturer": "Samsung",
"devices": [
{
"device": "Galaxy A32 5G",
"models": [
{
"model": "SM-A326B",
"data": [
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2022-01-01",
"fw_id": "A326BXXS4AVA1",
"android": "R(Android 11)",
"known_passcode": false,
"afu": false,
"bfu": false,
"bruteforce": false
},
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2021-09-01",
"fw_id": "A326BXXU3AUH7",
"android": "R(Android 11)",
"known_passcode": true,
"afu": false,
"bfu": true,
"bruteforce": true
}
]
},
{
"model": "SM-A326U1",
"data": [
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2021-09-01",
"fw_id": "A326U1UEU5AUJ2",
"android": "R(Android 11)",
"known_passcode": true,
"afu": false,
"bfu": true,
"bruteforce": true
}
]
}
]
}
]
},
{
"manufacturer": "LGE",
"devices": [
{
"device": "LG K31",
"models": [
{
"model": "SM-A326B",
"data": [
{
"chipset": "Mediatek MT6762",
"date": "unknown",
"fw_id": "L355DL10l",
"android": "unknown",
"known_passcode": false,
"afu": false,
"bfu": false,
"bruteforce": false
}
]
}
]
}
]
}
]
}
]
Working in React, here's what I've got so far in trying to massage this data:
const source = data;
const destination = [];
const classifiedTokens = []; // will be used to stored already classified tokens
const classifiedTokensModel = []; // will be used to stored already classified tokens for models
const getNextTokenArray = (source) => {
let unusedToken = null;
const nextTokenArray = source.filter(function (element) {
if (!unusedToken && !classifiedTokens.includes(element['device'])) {
unusedToken = element['device'];
classifiedTokens.push(unusedToken);
}
return unusedToken ? unusedToken === element['device'] : false;
});
return unusedToken ? nextTokenArray : null;
};
// Pass in arrays deconstructed from addToDestination to process third tier nested objects for models
const getNextTokenArrayModel = (tokenObject) => {
let tokenObjectDevice = tokenObject['device'];
let tokenObjectData = tokenObject['data'];
let unusedTokenModel = null;
const nextTokenArrayModel = tokenObjectData.filter(function (element) {
if (!unusedTokenModel && !classifiedTokensModel.includes(element['model'])) {
unusedTokenModel = element['model'];
classifiedTokensModel.push(unusedTokenModel);
}
return unusedTokenModel ? unusedTokenModel === element['model'] : false;
});
//return unusedTokenModel ? nextTokenArrayModel : null;
if (unusedTokenModel) {
if (nextTokenArrayModel.length === 0) return;
let res = {
device: tokenObjectDevice,
model: nextTokenArrayModel[0]['model'],
data: [],
};
nextTokenArrayModel.forEach((element) => {
res.data.push({
manufacturer: element.manufacturer,
chipset: element.chipset,
date: element.date,
fw_id: element.fw_id,
android: element.android,
knownPasscode: element.knownPasscode,
afu: element.afu,
bfu: element.bfu,
bruteforce: element.bruteforce,
});
});
destination.push(res);
} else {
return null;
}
};
const addToDestination = (tokenArray) => {
if (tokenArray.length === 0) return;
let res = {
device: tokenArray[0]['device'],
data: [],
};
tokenArray.forEach((element) => {
res.data.push({
manufacturer: element.manufacturer,
model: element.model,
chipset: element.chipset,
date: element.date,
fw_id: element.fw_id,
android: element.android,
knownPasscode: element.knownPasscode,
afu: element.afu,
bfu: element.bfu,
bruteforce: element.bruteforce,
});
});
getNextTokenArrayModel(res); // Call this to process and group nested model duplicates by device
//destination.push(res);
};
let nextTokenArray = getNextTokenArray(source);
while (nextTokenArray) {
addToDestination(nextTokenArray);
nextTokenArray = getNextTokenArray(source);
}
setTimeout(() => {
document.getElementById('root').innerHTML =
'<pre>' + JSON.stringify(destination, null, 2) + '</pre>';
}, 1000);
};
And here's the JSFiddle again:
https://jsfiddle.net/xpancom/Lq7duahv/
Who can smash this data formatting dilemma?
This answer is not React specific, but one approach would be to use array.reduce() to transform each level/node of the structure as shown in the code snippet below.
const source = [
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326B',
chipset: 'Mediatek MT6853V/NZA',
date: '2022-01-01',
fw_id: 'A326BXXS4AVA1',
android: 'R(Android 11)',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326B',
chipset: 'Mediatek MT6853V/NZA',
date: '2022-01-01',
fw_id: 'A326BXXS4AVA1',
android: 'R(Android 11)',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326U',
chipset: 'Mediatek MT6853V/NZA',
date: '2021-03-01',
fw_id: 'A326USQU1AUD4',
android: 'R(Android 11)',
known_passcode: true,
afu: false,
bfu: true,
bruteforce: true,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326U1',
chipset: 'Mediatek MT6853V/NZA',
date: '2021-09-01',
fw_id: 'A326U1UEU5AUJ2',
android: 'R(Android 11)',
known_passcode: true,
afu: false,
bfu: true,
bruteforce: true,
},
{
manufacturer: 'LGE',
device: 'LG K31',
model: 'LGL355DL',
chipset: 'Mediatek MT6762',
date: 'unknown',
fw_id: 'L355DL10l',
android: 'unknown',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
];
function generateTree(data, key) {
return data.reduce((acc, val) => {
// Split the key name from the child data
const { [key.name]: keyName, ...childData } = val;
// Find a tree item in the structure being generated
const treeItem = acc.find((item) => item[key.name] === keyName);
if (treeItem) {
// If found, append child data
treeItem[key.child].push(childData);
} else {
// If not found, create new key and append child data
acc.push({ [key.name]: keyName, [key.child]: [childData] });
}
return acc;
}, []);
}
// Generate manufacturer/device structure
const manufacturers = generateTree(source, {
name: 'manufacturer', // Key name to use as grouping identifier
child: 'devices', // Key name for child data
});
// Generate device/model structure
manufacturers.forEach((manufacturer) => {
manufacturer.devices = generateTree(manufacturer.devices, {
name: 'device',
child: 'models',
});
// Generate model/data structure
manufacturer.devices.forEach((device) => {
device.models = generateTree(device.models, {
name: 'model',
child: 'data',
});
});
});
const destination = [{ manufacturers }];
console.log(destination);

JavaScript: How to update the values of a nested array (linter throwing 'no-param-reassign' warning)

I have an array that looks like this:
const MENUS_LIST: MenuListing[] = [
{
id: 1,
name: 'Analytics',
defaultPath: '/analytics/sales/overview',
relatedPath: ['/analytics/sales/overview', '/analytics/sales/bookings'],
submenu: [
{
label: 'Overview',
path: '/analytics/sales/overview',
relatedPath: ['/analytics/sales/overview'],
additionalIcon: [],
name: ['Overview'],
id: 'sales-overview',
},
{
label: 'Bookings',
path: '/analytics/sales/bookings',
relatedPath: ['/analytics/sales/bookings'],
additionalIcon: [],
name: ['Bookings'],
id: 'sales-bookings',
},
],
},
];
I need to convert it to the following format - by adding the isActive flag to the main structure and submenu when the current path === submenu.path.
In the following example, we assume path to be /analytics/sales/overview.
[
{
id: 1,
name: 'Analytics',
defaultPath: '/analytics/sales/overview',
relatedPath: ['/analytics/sales/overview', '/analytics/sales/bookings'],
isActive: true,
submenu: [
{
label: 'Overview',
path: '/analytics/sales/overview',
relatedPath: ['/analytics/sales/overview'],
additionalIcon: [],
name: ['Overview'],
id: 'sales-overview',
isActive: true,
},
{
label: 'Bookings',
path: '/analytics/sales/bookings',
relatedPath: ['/analytics/sales/bookings'],
additionalIcon: [],
name: ['Bookings'],
id: 'sales-bookings',
isActive: false,
},
],
},
];
I have the following solution which works (code is simplified):
menuX = (MENUS_LIST as MenuListingProps[]).map((m: MenuListingProps) => {
const resultX = { ...m };
resultX.isActive = true; // will perform checking to determine true or false
(m.submenu as MenuItemProps[]).forEach((sm: MenuItemProps) => {
sm.isActive = true; // linter warning; value assigned based on checking (can be true or false)
sm.icon = 'abc'; // linter warning
sm.title = 'xyz'; // linter warning
});
return resultX;
});
But the linter is complaining of Assignment to property of function parameter "sm" on the lines where I'm assigning values to sm
Based on this SO post, I understand that I need to copy the argument to a temporary variable and work on it instead.
I did this by creating a new var resultX. But I'm not sure how to go about doing the same with sm.
Seeking some guidance, thank you.
menuX = (MENUS_LIST as MenuListingProps[]).map((m: MenuListingProps) => {
const resultX = { ...m };
resultX.isActive = true; // will perform checking to determine true or false
resultX.submenu = (m.submenu as MenuItemProps[]).map((sm: MenuItemProps) => {
const sub = {...sm};
sub.isActive = true; // linter warning; value assigned based on checking (can be true or false)
sub.icon = 'abc'; // linter warning
sub.title = 'xyz'; // linter warning
return sub;
});
return resultX;
});
Here's a method using Object.assign.
Object.assign doesn't mutate the original object, so it returns a new object with the given changes.
const MENUS_LIST = [{"id":1,"name":"Analytics","defaultPath":"/analytics/sales/overview","relatedPath":["/analytics/sales/overview","/analytics/sales/bookings"],"submenu":[{"label":"Overview","path":"/analytics/sales/overview","relatedPath":["/analytics/sales/overview"],"additionalIcon":[],"name":["Overview"],"id":"sales-overview"},{"label":"Bookings","path":"/analytics/sales/bookings","relatedPath":["/analytics/sales/bookings"],"additionalIcon":[],"name":["Bookings"],"id":"sales-bookings"}]}];
const menuX = MENUS_LIST.map(menu => Object.assign(menu, {
isActive: true, // will perform checking to determine true or false
submenu: menu.submenu.map(submenu => Object.assign(submenu, {
isActive: true, // will perform checking to determine true or false
icon: 'abc', // linter warning
title: 'xyz' // linter warning
}))
}));
console.log(menuX);
I hope this code helping you
var array = [
{
id: 1,
name: 'Analytics',
defaultPath: '/analytics/sales/overview',
relatedPath: ['/analytics/sales/overview', '/analytics/sales/bookings'],
isActive: true,
submenu: [
{
label: 'Overview',
path: '/analytics/sales/overview',
relatedPath: ['/analytics/sales/overview'],
additionalIcon: [],
name: ['Overview'],
id: 'sales-overview',
isActive: true,
},
{
label: 'Bookings',
path: '/analytics/sales/bookings',
relatedPath: ['/analytics/sales/bookings'],
additionalIcon: [],
name: ['Bookings'],
id: 'sales-bookings',
isActive: false,
},
],
},
];
array.map(o=>o.submenu.map(v=> o.defaultPath == v.path? {...v,isActive:true,icon:"abc",title:'xyz'}:{...v,isActive:false,icon:"abc",title:'xyz'}))

ES5 only: return value when match is found after iterating over various values in an array

I am writing a function to return an id based on a few various things aligning. The code mostly works except that because there is no break; for a forEach. I believe I need to use a different filter or find option on the array.
function getDefaultId(prod) {
var defaultId;
prod.images.forEach( function(image) {
defaultId = image.isPrimary ? image.id : undefined;
});
return defaultId;
}
var prod.images = [
0: {
isPrimary: false,
id: 1234
},
1: {
isPrimary: true,
id: 1235
},
2: {
isPrimary: false,
id: 1236
}
]
Essentially I'm trying to return the corresponding id for isPrimary. The result should be 1236 but I'm getting undefined because the forEach is not breaking and thus is resetting the variable to undefined on the next iteration.
You could use Array#some and exit early.
function getDefaultId(prod) {
var defaultId;
prod.images.some(function (image) {
if (image.isPrimary) {
defaultId = image.id;
return true;
}
});
return defaultId;
}
var prod = { images: [{ isPrimary: false, id: 1234 }, { isPrimary: true, id: 1235 }, { isPrimary: false, id: 1236 } ]};
console.log(getDefaultId(prod));
var images = [
{
isPrimary: false,
id: 1234
},
{
isPrimary: true,
id: 1235
},
{
isPrimary: false,
id: 1236
}
]
let result = images.filter(img => img.isPrimary === true)
console.log(result)
You could use .filter() and to get an array of objects which have isPrimary as true, and then return the id of the first item found like so:
function getDefaultId(prod) {
var primaryImg = prod.images.filter(function(img) {
return img.isPrimary;
});
return primaryImg[0].id;
}
var prod = {};
prod.images = [{isPrimary: false, id: 1234 }, {isPrimary: true, id: 1235}, {isPrimary: false, id: 1236}];
console.log(getDefaultId(prod));
Give it a try using map.
var images = [
{
isPrimary: false,
id: 1234
},
{
isPrimary: true,
id: 1235
},
{
isPrimary: false,
id: 1236
}
];
function getDefaultId() {
var defaultId;
images.map(function (image) {
if (image.isPrimary) {
defaultId = image.id;
}
});
return defaultId;
}
console.log(getDefaultId());

Ramda.js Combine two array of objects that share the same property ID

I have these two array of objects
todos: [
{
id: 1,
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
name: 'handover',
label: 'Handover (in CRM)'
},
]
And:
todosMoreDetails: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}
]
So that the final array of objects will be a combination of the two, based on the object ID, like below:
FinalTodos: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'handover',
label: 'Handover (in CRM)'
}
]
I tried with merge mergeAll and mergeWithKey but I am probably missing something
You can achieve this with an intermediate groupBy:
Transform the todosMoreDetails array into an object keyed by todo property ID using groupBy:
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
moreDetailsById is an object where the key is id, and the value is an array of todos. If the id is unique, this will be a singleton array:
{
1: [{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}]
}
Now transform the todos array by merging each todo to it's details you retrieve from the grouped view:
var finalTodos = R.map(todo => R.merge(todo, moreDetailsById[todo.id][0]), todos);
An alternate more detailed way:
function mergeTodo(todo) {
var details = moreDetailsById[todo.id][0]; // this is not null safe
var finalTodo = R.merge(todo, details);
return finalTodo;
}
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
var finalTodos = todos.map(mergeTodo);
I guess merge is only used for arrays. Have a search for object "extend". Maybe storing the todo details not in seperate objects is the better solution.
Using jQuery? https://api.jquery.com/jquery.extend/
Using underscore? http://underscorejs.org/#extend
Native approach? https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
Using underscore:
var result = [];
var entry = {};
_.each(todos, function(todo) {
_.each(todosMoreDetails, function(detail) {
if (todo.id == detail.id) {
entry = _.extend(todo, detail);
result.push(entry);
}
}
});
return result;

Meteor cross collection arrays

I am trying to pull an array from a different collection using collection2. I have been able to do this with objects using the following example for users:
users: {
type: String,
label: "Inspector",
optional: true,
autoform: {
firstOption: 'Choose an Inspector',
options: function() {
return Meteor.users.find({}, {
sort: {
profile: 1,
firstName: 1
}
}).map(function(c) {
return {
label: c.profile.firstName + " " + c.profile.lastName,
value: c._id
};
});
}
}
},
I would like to do the same but for an array of objects. Here is what the source data looks like:
{
"_id": "xDkso4FXHt63K7evG",
"AboveGroundSections": [{
"sectionName": "one"
}, {
"sectionName": "two"
}],
"AboveGroundItems": [{
"itemSection": "one",
"itemDescription": "dfgsdfg",
"itemCode": "dsfgsdg"
}, {
"itemSection": "two",
"itemDescription": "sdfgsdfg",
"itemCode": "sdfgsdgfsd"
}]
}
Here is what my function looks like:
agSection: {
type: String,
optional: true,
autoform: {
firstOption: 'Select A Section Type',
options: function() {
return TemplateData.find({}, {
sort: {
AboveGroundSections: 1,
sectionName: [0]
}
}).map(function(c) {
return {
label: c.AboveGroundSections.sectionName,
value: c.AboveGroundSections.sectionName
}
});
}
}
},
I know this, it's just not pulling the data for me. I am sure, I am just missing something small. I am trying to pull all objects within the AboveGroundSection array.
Your .map() is iterating over the set of documents but not over the arrays inside each document. Also I don't think your sorting is going to work the way you hope because of the inner nesting.
Try:
agSection: {
type: String,
optional: true,
autoform: {
firstOption: 'Select A Section Type',
options() {
let opt = [];
TemplateData.find().forEach(c => {
c.AboveGroundSections.forEach(s => { opt.push(s.sectionName) });
});
return opt.sort().map(o => { return { label: o, value: o } });
}
}
},
Also if your AboveGroundSections array only has a single key per element then you can simplify:
"AboveGroundSections": [
{ "sectionName": "one" },
{ "sectionName": "two" }
]
To:
"AboveGroundSections": [
"one",
"two"
]

Categories