How to modify properties of nested objects? - javascript

Lets say I have the following object with nested objects:
const obj = {
Visualization: {
Lower: [{ name: "Part", selectedValue: "60-000" }],
Upper: [{ name: "Part", selectedValue: "60-000" }],
},
Holder: {
Lower: [
{ name: "Part", selectedValue: "30-000" },
{ name: "Project Name", selectedValue: "45-000" },
],
Upper: [
{ name: "Part", selectedValue: "22-000" },
{ name: "Project Name", selectedValue: "25-000" },
],
},
};
And I want to change the selectedValue of all of them at once to "0" and have the following result:
{
Visualization: {
Lower: [{ name: "Part", selectedValue: "0" }],
Upper: [{ name: "Part", selectedValue: "0" }],
},
Holder: {
Lower: [
{ name: "Part", selectedValue: "0" },
{ name: "Project Name", selectedValue: "0" },
],
Upper: [
{ name: "Part", selectedValue: "0" },
{ name: "Project Name", selectedValue: "0" },
],
},
}
How can I make it properly?
Thank you!

function deepSet(o, propertyName, propertyValue) {
Object.keys(o).forEach(key => {
if (key === propertyName) {
o[key] = propertyValue;
return;
}
if (Array.isArray(o[key])) {
o[key].forEach(item => {
deepSet(item, propertyName, propertyValue);
});
return;
}
if (typeof o[key] !== 'object') {
return;
}
deepSet(o[key], propertyName, propertyValue);
});
}
JSFiddle: https://jsfiddle.net/713vfdrg/

You can handle this recursively and update the original object.
All I did was check to see if the object is an Array. If it is then you loop through the array and update each objects selectedValue to be 0. This will update those keys arrays.
If it is not an array then that means you are dealing with an object, either the parent or children object, so then you loop over the keys and drill down the object structure for each key.
This function below assumes that the data will be in that format you have shown above.
const obj = {
Visualization: {
Lower: [{ name: "Part", selectedValue: "60-000" }],
Upper: [{ name: "Part", selectedValue: "60-000" }],
},
Holder: {
Lower: [
{ name: "Part", selectedValue: "30-000" },
{ name: "Project Name", selectedValue: "45-000" },
],
Upper: [
{ name: "Part", selectedValue: "22-000" },
{ name: "Project Name", selectedValue: "25-000" },
],
},
};
function updateSelectedValue(obj, keyToUpdate, valueUpdate) {
if(Array.isArray(obj)){
obj.forEach((content, idx) => {
if(obj[idx][keyToUpdate]){
obj[idx][keyToUpdate] = valueUpdate;
}
})
return;
}
const keys = Object.keys(obj);
for(const key in obj){
updateSelectedValue(obj[key], keyToUpdate, valueUpdate);
}
}
updateSelectedValue(obj, "selectedValue", 0)
console.log(obj)

Related

Refactor Javascript Object Traversal

The code that I have does result in the desired output. However, in my opinion, using these nested for loops is a little ugly and I am trying to refactor my work.
My question is: do you have any suggestions on refactoring the code so that I can avoid the need of using these nested for loops?
I want to loop over a nested object and end up with a result of all unique keys, and an array of all values for that unique key.
{"ABC":["1","100","6"],"DEF":["10","2","5"],"GHI":["14","9"],"HGI":["4"]}
const data = {
something: [
{
innerSomething: {
list: [
{
title: "ABC",
amount: "1"
},
{
title: "DEF",
amount: "10"
},
{
title: "GHI",
amount: "14"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "100"
},
{
title: "DEF",
amount: "2"
},
{
title: "GHI",
amount: "9"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "6"
},
{
title: "DEF",
amount: "5"
},
{
title: "HGI",
amount: "4"
}
],
}
}
]
};
const results = {};
data.something.forEach((item) => {
item.innerSomething.list.forEach((list) => {
if (results[list.title]) {
// exists already, just push the amount
results[list.title].push(list.amount)
} else {
// Is unique so far, add it to the object
results[list.title] = [list.amount];
}
})
});
console.log(`results: ${JSON.stringify(results)}`);
// These results are the correct and desired output
// {"ABC":["1","100","6"],"DEF":["10","2","5"],"GHI":["14","9"],"HGI":["4"]}
Your implementation is ok, tbh.
The only thing I can suggest differently is to make use of the neat destructuring patterns and spread syntax, considering that the structure of your input object is very well known:
data.something.forEach(({ innerSomething: { list } }) =>
list.forEach(({ title, amount }) =>
results[title] = [...results[title] || [], amount]))
For what it's worth, here is how I'd write it.
const data = {
something: [
{
innerSomething: {
list: [
{
title: "ABC",
amount: "1"
},
{
title: "DEF",
amount: "10"
},
{
title: "GHI",
amount: "14"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "100"
},
{
title: "DEF",
amount: "2"
},
{
title: "GHI",
amount: "9"
}
],
}
},
{
innerSomething: {
list: [
{
title: "ABC",
amount: "6"
},
{
title: "DEF",
amount: "5"
},
{
title: "GHI",
amount: "4"
}
],
}
}
]
};
const results = {};
for (let something of data.something) {
for (let item of something.innerSomething.list) {
const { title, amount } = item;
results[title] = results[title] || [];
results[title].push(amount);
}
}
console.log(`results: ${JSON.stringify(results)}`);

create and count dynamic object in typescript

I am trying to compute a result from an array for objects that I can count how many values for each key and add its own dbId to the same computed result within the object name!
the reason behind that is to use it to chart.js. so I can check each result value comes with its own dbIds
const obj = [
{
dbId: 26598,
properties: [
{ attributeName: "BIM7AATypeCode" },
{ displayCategory: "Identity Data" },
{ displayName: "BIM7AATypeCode" },
{ displayValue: "221" },
],
},
{
dbId: 26591,
properties: [
{ attributeName: "BIM7AATypeCode" },
{ displayCategory: "Identity Data" },
{ displayName: "BIM7AATypeCode" },
{ displayValue: "221" },
],
},
{
dbId: 3695,
properties: [
{ attributeName: "abc" },
{ displayCategory: "Identity Data" },
{ displayName: "BIM7AATypeCode" },
{ displayValue: "123" },
],
},
{
dbId: 3697,
properties: [
{ attributeName: "abc" },
{ displayCategory: "Identity Data" },
{ displayName: "BIM7AATypeCode" },
{ displayValue: "123" },
],
},
];
// type Histogram = {
// [key: string]: number;
// };
let histogram = {}; //as Histogram
for (let iterator of obj) {
for (let { displayName, displayValue } of iterator.properties) {
//#ts-ignore
histogram[displayName] = histogram[displayName] | {};
if (!histogram[displayValue]) {
histogram[displayValue] = 1;
} else {
histogram[displayValue] = histogram[displayValue] + 1;
}
}
}
console.log(histogram);
//expected result:
/*
{
BIM7AATypeCode: {
"221": 2,
dbid: [26598, 26591],
},
abc: {
"123": 2,
dbid: [3695, 3697],
},
};
*/
Please find the Array.reduce implementation.
Logic
Loop through obj array using Array.reduce
From each object in the array, pick the properties key. From this select node having key attributeName which is attribute node and with displayValue which is display value node.
Check whether the accumulator has a node with the attributeName.
If not, push a node to accumulator, with the displayValue having vale 1 and dbid as a single noded array.
If accumulator already have this node. Update the count of displayValue and push the new dbid
const obj = [
{ dbId: 26598, properties: [{ attributeName: "BIM7AATypeCode" }, { displayCategory: "Identity Data" }, { displayName: "BIM7AATypeCode" }, { displayValue: "221" }] },
{ dbId: 26591, properties: [{ attributeName: "BIM7AATypeCode" }, { displayCategory: "Identity Data" }, { displayName: "BIM7AATypeCode" }, { displayValue: "221" }] },
{ dbId: 3695, properties: [{ attributeName: "abc" }, { displayCategory: "Identity Data" }, { displayName: "BIM7AATypeCode" }, { displayValue: "123" }] },
{ dbId: 3697, properties: [{ attributeName: "abc" }, { displayCategory: "Identity Data" }, { displayName: "BIM7AATypeCode" }, { displayValue: "123" }] },
];
const output = obj.reduce((acc, curr) => {
const attribute = curr.properties.find(node => node.attributeName);
const displayValue = curr.properties.find(node => node.displayValue);
if (acc[attribute.attributeName]) {
acc[attribute.attributeName][displayValue.displayValue] ? acc[attribute.attributeName][displayValue.displayValue]++ : acc[attribute.attributeName][displayValue.displayValue] = 1;
acc[attribute.attributeName].dbid.push(curr.dbId);
} else {
const newAttribute = {
[displayValue.displayValue]: 1,
dbid: [curr.dbId]
}
acc[attribute.attributeName] = newAttribute;
}
return acc;
}, {});
console.log(output);

how to apply recursive function on json data in javascript

I've validation schema in js like this
var data = [
{
id: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
},
{
name: {
fields: {
firstName: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
lastName: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
midName: {
label: "",
tests: [],
},
},
tests: [],
},
},
{
email: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
},
{
city: {
label: "",
tests: [],
},
},
{
details: {
fields: {
age: {
label: "",
tests: [
{
name: "max",
params: {
max: 18,
},
},
],
},
tests: [],
},
},
},
];
I only want the name of the keys from array which has required field like this
var selected= ["id", "name", "email"].
For this want to create one dynamic function like if data has key:"tests" 'required' it will return the name and if data has key:"fields" it will check again for the 'tests'.
var data = [
{
id: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
},
{
name: {
fields: {
firstName: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
lastName: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
midName: {
label: "",
tests: [],
},
},
tests: [],
},
},
{
email: {
label: "",
tests: [
{
name: "required",
params: "",
},
],
},
},
{
city: {
label: "",
tests: [],
},
},
{
details: {
fields: {
age: {
label: "",
tests: [
{
name: "max",
params: {
max: 18,
},
},
],
},
tests: [],
},
},
},
]
console.clear()
const keys = data.map(x => recursive(Object.keys(x)[0], x)).filter(x => x !== undefined)
function recursive (name, x) {
for (let key in x) {
if (x[key].tests !== undefined && typeof x[key].tests === "object") {
const find = x[key].tests.find(y => y.name === "required")
if (find) return name
}
if (x[key].fields !== undefined) {
for (let y in x[key].fields) {
if (y !== "tests") {
console.log(y)
const v = recursive(parent, x[key].fields)
if (v !== undefined) return name
}
}
}
}
}
console.log("KEYS", keys)
Like you said search recursive in child if we have only one time require in namefield. Just you need to remember the name of your data and filter at the end, because i don't return a name so that return undefined if i didn't find this specific string field.

Create csv out of array of objects with nested array of objects javascript

So I'm trying to take these two pieces of data
const headers = [
{ label: 'Item/Passage', key: 'objectType' },
{ label: 'Constraint Type', key: 'constraintType' },
{ label: 'Constraint Name', key: 'description' },
{ label: 'Lower', key: 'lowerBound' },
{ label: 'Upper', key: 'upperBound' },
{ label: 'Target Attribute', key: 'attributeName' },
{ label: 'Theta', key: 'referenceValue' },
{ label: 'Form/Passage', key: 'scope' },
{ label: 'Filter', key: 'filters' },
{ label: 'Filter values', key: 'filterValues' },
{ label: 'Min', key: 'min' },
{ label: 'Max', key: 'max' },
];
const array = [
{
"objectType": "Item",
"constraintType": "Include",
"description": "constraint1",
"lowerBound": "1",
"filters": [
{
"attributeName": "Item Identifier",
"values": "I105_15201|I105_15202",
"valueLowerBound": null,
"valueUpperBound": null
},
{
"attributeName": "Passage Item Order",
"values": "5|1|3|4|6|7|8|9|10|11|12|13|14|15|16|None",
"valueLowerBound": null,
"valueUpperBound": null
}
],
"upperBound": "4",
"attributeName": null,
"referenceValue": "",
"scope": null
},
{
"objectType": "Passage",
"constraintType": "Include",
"description": "constraint2",
"lowerBound": "1",
"filters": [
{
"attributeName": "Passage Identifier",
"values": "pid_1-1|pid_10-1|pid_2-1|pid_4-1|pid_5-1|pid_7-1|pid_8-1|pid_9-1",
"valueLowerBound": null,
"valueUpperBound": null
},
{
"attributeName": "Word Count",
"values": "",
"valueLowerBound": 3,
"valueUpperBound": 234
}
],
"upperBound": "4",
"attributeName": null,
"referenceValue": "",
"scope": null
},
{
"objectType": "Item",
"constraintType": "Include",
"description": "constraint3",
"filters": [],
"lowerBound": "1",
"upperBound": "4",
"attributeName": null,
"referenceValue": "",
"scope": null
}
]
And produce a csv file that looks like this,
Basically, I take the label values from the header array, create the header, then each constraint is put on its own row. If a constraint has filters, another row is created and the values are placed within the last four columns. For example, constraint1 and 2 have two filters, constraint 3 has none.
I've been able to accomplish already with the following code, but feel it's not a terribly stable implementation. Looking for any suggestions on how to implement this. Thanks!
Here is the copy of the string I've been able to create
toCsv -> csv Item/Passage,Constraint Type,Constraint Name,Lower,Upper,Target Attribute,Theta,Form/Passage,Filter,Filter values,Min,Max
Item,Include,constraint1,1,4,,,
,,,,,,,,Item Identifier,I105_15201|I105_15202,,
,,,,,,,,Passage Item Order,5|1|3|4|6|7|8|9|10|11|12|13|14|15|16|None,,
Passage,Include,constraint2,1,4,,,
,,,,,,,,Passage Identifier,pid_1-1|pid_10-1|pid_2-1|pid_4-1|pid_5-1|pid_7-1|pid_8-1|pid_9-1,,
,,,,,,,,Word Count,,3,234
Item,Include,constraint3,1,4,,,
export const toCsv = (array, headers) => {
const getValuesFromObject = (obj) => {
if (typeof obj !== 'object' || obj === null) {
return [];
}
const headerKeys = Object.keys(headers);
const keys = Object.keys(obj);
const values = [];
const filterValues = [];
for (var i = 0; i < keys.length; ++i) {
if (Array.isArray(obj[keys[i]])) {
obj[keys[i]].map((filterObj) => {
filterValues.push(',,,,,,,,' + Object.values(filterObj)); // this part bothers me the most. I use it to create empty cells for the filter rows.
});
} else {
values.push(obj[keys[i]]);
}
}
return [].concat([values]).concat(filterValues).join('\n');
};
const csvHeaders = headers.map((obj) => obj.label).join(',');
const body = array.map(getValuesFromObject);
let csv = [].concat([csvHeaders]).concat(body).join('\n');
return csv;
};
You could take a single loop for the objects, their filters and iterate the keys.
const
toCsv = (array, headers) => {
const getValuesFromObject = o => !o || typeof o !== 'object'
? []
: [
headers.map(({ key }) => key !== 'filters' && o[key] || '').join(),
...o.filters.map(q => headers.map(({ key }) => q[key] || '').join())
];
return [
headers.map((obj) => obj.label).join(','),
...array.flatMap(getValuesFromObject)
].join('\n');
},
headers = [{ label: 'Item/Passage', key: 'objectType' }, { label: 'Constraint Type', key: 'constraintType' }, { label: 'Constraint Name', key: 'description' }, { label: 'Lower', key: 'lowerBound' }, { label: 'Upper', key: 'upperBound' }, { label: 'Target Attribute', key: 'attributeName' }, { label: 'Theta', key: 'referenceValue' }, { label: 'Form/Passage', key: 'scope' }, { label: 'Filter', key: 'filters' }, { label: 'Filter values', key: 'values' }, { label: 'Min', key: 'valueLowerBound' }, { label: 'Max', key: 'valueUpperBound' }],
array = [{ objectType: "Item", constraintType: "Include", description: "constraint1", lowerBound: "1", filters: [{ attributeName: "Item Identifier", values: "I105_15201|I105_15202", valueLowerBound: null, valueUpperBound: null }, { attributeName: "Passage Item Order", values: "5|1|3|4|6|7|8|9|10|11|12|13|14|15|16|None", valueLowerBound: null, valueUpperBound: null }], upperBound: "4", attributeName: null, referenceValue: "", scope: null }, { objectType: "Passage", constraintType: "Include", description: "constraint2", lowerBound: "1", filters: [{ attributeName: "Passage Identifier", values: "pid_1-1|pid_10-1|pid_2-1|pid_4-1|pid_5-1|pid_7-1|pid_8-1|pid_9-1", valueLowerBound: null, valueUpperBound: null }, { attributeName: "Word Count", values: "", valueLowerBound: 3, valueUpperBound: 234 }], upperBound: "4", attributeName: null, referenceValue: "", scope: null }, { objectType: "Item", constraintType: "Include", description: "constraint3", filters: [], lowerBound: "1", upperBound: "4", attributeName: null, referenceValue: "", scope: null }],
result = toCsv(array, headers);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Setting array keys dynamically based on length

Given an array in this format:
[
[{
name: "name",
value: "My-name"
},
{
name: "qty",
value: "1"
},
{
name: "url",
value: "test.com"
},
{
name: "comment",
value: "my-comment"
}
],
[{
name: "name",
value: "My-name2"
},
{
name: "qty",
value: "3"
},
{
name: "url",
value: "test2.com"
}
],
[{
name: "name",
value: "My-name3"
},
{
name: "qty",
value: "1"
},
{
name: "url",
value: "test3.com"
},
{
name: "comment",
value: "my-comment3"
}
]
]
I'm looking to switch that to:
[
[
{ name: "My-name" },
{ qty: "1" },
{ url: "test.com" },
{ comment: "my-comment", }
],[
{ name: "My-name2" },
{ qty: "3" },
{ url: "test2.com",
],[
{ name: "My-name3", },
{ qty: "1", },
{ url: "test3.com", },
{ comment: "my-comment3", }
]
]
In other words, swapping out the array keys but maintaining the object structure within each array element.
I've tried looping over each element and can swap the keys out using something like:
newArray[iCount][item.name] = item.value;
However I'm then struggling to preserve the object order. Note that the comment field may or may not appear in the object.
With Array.map() function:
var arr = [
[{name:"name",value:"My-name"},{name:"qty",value:"1"},{name:"url",value:"test.com"},{name:"comment",value:"my-comment"}],
[{name:"name",value:"My-name2"},{name:"qty",value:"3"},{name:"url",value:"test2.com"}],
[{name:"name",value:"My-name3"},{name:"qty",value:"1"},{name:"url",value:"test3.com"},{name:"comment",value:"my-comment3"}]
],
result = arr.map(function(a){
return a.map(function(obj){
var o = {};
o[obj.name] = obj.value
return o;
});
});
console.log(result);
Check my moreBetterOutput value. I think will be better.
If you still need a result like your example in the question then you can check output value.
const input = [
[
{
name:"name",
value:"My-name"
},
{
name:"qty",
value:"1"
},
{
name:"url",
value:"test.com"
},
{
name:"comment",
value:"my-comment"
}
],
[
{
name:"name",
value:"My-name2"
},
{
name:"qty",
value:"3"
},
{
name:"url",
value:"test2.com"
}
],
[
{
name:"name",
value:"My-name3"
},
{
name:"qty",
value:"1"
},
{
name:"url",
value:"test3.com"
},
{
name:"comment",
value:"my-comment3"
}
]
]
const output = input.map(arr => arr.map(obj => ({[obj.name]: obj.value})))
const moreBetterOutput = output.map(arr => arr.reduce((acc, item, index) => {
acc[Object.keys(item)[0]] = item[Object.keys(item)[0]];
return acc;
}, {}) )
//console.log(output);
console.log(moreBetterOutput);
Another map function:
const result = array.map( subarray =>
Object.assign(...subarray.map( ({name, value}) => ({ [name] : value }) ))
);

Categories

Resources