Going through the link Merge/flatten an array of arrays in JavaScript? is somewhat what i need. But with that link and many other links shows merging of arrays of two arrays. What I have is as follows
[
{
"setter":[
{
"keyname":"Sample Size",
"cond":"=",
"value":1
}
]
},
{
"setter":[
{
"joinedcond":"and"
},
{
"keyname":"Sample Size",
"cond":"=",
"value":2
}
]
}
]
That is I have an array and inside that array I have an array "setter".
What I want is actually merging all setter array as a single array. Having said the merge should produce below output
[
{
"setter":[
{
"keyname":"Sample Size",
"cond":"=",
"value":1
},
{
"joinedcond":"and"
},
{
"keyname":"Sample Size",
"cond":"=",
"value":2
}
]
}
]
Help would be appreciated. Thanks
You can do it by using Array#reduce
var arr = [{"setter":[{"keyname":"Sample Size","cond":"=","value":1}]},{"setter":[{"joinedcond":"and"},{"keyname":"Sample Size","cond":"=","value":2}]}];
var finalArr = arr.reduce((a,x)=>{
(a[0].setter = a[0].setter || []).push(...x.setter);
return a;
},[{}]);
console.log(finalArr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use a hash table for the outer keys and concat the inner values with a dynamic approach for the outer keys, like setter.
var data = [{ setter: [{ keyname: "Sample Size", cond: "=", value: 1 }] }, { setter: [{ joinedcond: "and" }, { keyname: "Sample Size", cond: "=", value: 2 }] }],
result = data.reduce(function (hash) {
return function (r, o) {
Object.keys(o).forEach(function (k) {
if (!hash[k]) {
hash[k] = {};
r.push(hash[k]);
}
hash[k][k] = (hash[k][k] || []).concat(o[k]);
});
return r;
};
}(Object.create(null)), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using ES6 Rest and Spread operators we can achieve the same with a recursive function call:
let cdata= data.slice();//Copy the data
let condensedArray=[];
function flatten(data, ...rest){
let [{ setter }] = data;
condensedArray = [...condensedArray, ...setter];
data.splice(0,1);
data.length>0?flatten(data, ...data):console.log('Complete');
}
flatten(cdata, ...cdata);
console.log(condensedArray);
Related
Sorry for the potentially confusing question. What I mean is this:
I've been tasked to rename a specific set of object keys on a massively nested JSON object I've converted from XML. These keys are scattered throughout the nested object, buried under both arrays and objects. Let me show you what I mean:
const obj = {
"Zone": [
{
ExtWall: [
{ Win: "10" }
],
HVACDist: {
HVACCool: [
{ Win: "10" }
]
}
}
]
};
In this case, I need to rename the key Win to ResWin. However, this tag Win is scattered across a nested object far deeper than this.
I'm currently using JSONPath to find the path of these keys. For example:
var WinPath = jp.paths(object, '$..Win');
// Returns - [
// [ '$', 'Zone', 0, 'ExtWall', 0, 'Win' ],
// [ '$', 'Zone', 0, 'HVACDist', 'HVACCool', 0, 'Win' ]
// ]
JSONPath also has a stringify function which generates a path based on the paths array it produces. Let's focus on one element for now:
const pathStringified = jp.stringify(WinPath[0])
// Returns - $['Zone'][0]['ExtWall][0]['Win']
Somehow, I need to rename this Win key to ResWin.
Right now my best guess is to replace the $ with an empty string so I can access the objec for key replacement.
My goal:
obj['Zone'][0]['ExtWall][0]['ResWin'] = obj['Zone'][0]['ExtWall][0]['Win'];
delete obj['Zone'][0]['ExtWall][0]['Win'];
Any feedback or suggestions would be much appreciated.
Also, let me know how to restructure my question because I can understand how it would be difficult to understand.
Sounds like you're ok using a library, so here is a solution using object-scan.
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.3.11/lib/index.min.js';
const obj = { Zone: [{ ExtWall: [{ Win: '10' }], HVACDist: { HVACCool: [{ Win: '10' }] } }] };
const modify = objectScan(['**.Win'], {
filterFn: ({ parent, property, value }) => {
delete parent[property];
parent.ResWin = value;
},
rtn: 'count'
});
const r = modify(obj);
console.log(r); // number of matches
// => 2
console.log(obj);
// => { Zone: [ { ExtWall: [ { ResWin: '10' } ], HVACDist: { HVACCool: [ { ResWin: '10' } ] } } ] }
</script>
Disclaimer: I'm the author of object-scan
The syntax is very similar to the JSONPath sytax, but it provides callbacks. The code could easily be generified to allow renaming of multiple keys in a single iteration.
Note that obj is modified in place. You could do a copy before modification, if that is not desired (e.g. using lodash cloneDeep)
If you want to replace all keys with same name you can iterate the entire tree.
If you want to rename by path, basically loop the path drilling down until you are at the requested key.
const obj = {
"Zone": [{
ExtWall: [{
Win: "10"
}],
HVACDist: {
HVACCool: [{
Win: "10"
}]
}
}]
};
var WinPath = [
['$', 'Zone', 0, 'ExtWall', 0, 'Win'],
['$', 'Zone', 0, 'HVACDist', 'HVACCool', 0, 'Win']
];
function rename_by_path(obj, arr_path, new_name) {
var pointer = obj;
var dollar = arr_path.shift();
var key = arr_path.shift();
while (arr_path.length) {
pointer = pointer[key]
key = arr_path.shift();
}
pointer[new_name] = pointer[key]
delete pointer[key]
}
function rename_all(obj, key_name, new_name) {
const iterate = (obj) => {
if (!obj) {
return;
}
Object.keys(obj).forEach(key => {
var value = obj[key]
if (typeof value === "object" && value !== null) {
iterate(value)
}
if (key == key_name) {
obj[new_name] = value;
delete obj[key];
}
})
}
iterate(obj)
}
function rename_by_paths(obj, arr_paths, new_name) {
arr_paths.forEach(arr_path => rename_by_path(obj, arr_path, new_name))
}
rename_by_paths(obj, WinPath, "ResWin")
console.log(obj)
rename_all(obj, "ResWin", "ResWin2");
console.log(obj)
.as-console-wrapper {
max-height: 100% !important;
}
Base Object :
obj = {
"place": "{{base_gplaceId}}",
"feedInputs": [
{
"subCategoryQuestion": "{{base_gquestionId}}",
"context": "other",
"image": "abc.jpg",
"mediaMetadata": {
"stickerList": [
{
"id": "someid2",
"sticker": "delish",
"weight": 3
}
],
"textList": [
{
"text": "What an evening!!!"
}
]
}
}
]
};
more keys can have more nesting,
want to set the values of keys = "", one by one and push the updated object to an array
Expected OP :
[
{"place":"","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]}
,...........]
tried couple of recursions, but not able to break after update inside the nested objects,
any simplistic approach ?
You could iterate the properties and change the values who are not objects. For having access to the complete object store the root as well and take a copy of the object with stringify and parse for the result set.
function visitAll(object, root = object) {
return Object
.keys(object)
.flatMap(k => {
if (object[k] && typeof object[k] === 'object') return visitAll(object[k], root);
const value = object[k];
object[k] = '';
const result = JSON.parse(JSON.stringify(root));
object[k] = value;
return result;
});
}
var object = { place: "{{base_gplaceId}}", feedInputs: [{ subCategoryQuestion: "{{base_gquestionId}}", context: "other", image: "abc.jpg", mediaMetadata: { stickerList: [{ id: "someid2", sticker: "delish", weight: 3 }], textList: [{ text: "What an evening!!!" }] } }] },
result = visitAll(object);
result.forEach(o => console.log(JSON.stringify(o)));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I need to count each value on the object array , the desired output should be like below
[{
"question": "question1",
"USA": 2
}, {
"question": "question1",
"AUS": 1
},
{
"question": "question2",
"item1": 2
},
{
"question": "question2",
"item1,item2": 1
}, {
"question": "question4",
"3": 1
}, {
"question": "question4",
"2": 1
}
]
Below is the input I need to transform in to the above output. I have no clue how to do with n no of question and also got issue when one question has 2 answers . sample input
[{"question1":"USA","question2":["item1"],"question4":2},
{"question1":"USA","question2":["item1"],"question4":3},
{"question1":"AUS","question2":["item1","item2"]}];
let arr=[{"question1":"USA","question2":["item1"],"question4":2},{"question1":"USA","question2":["item1"],"question4":3},{"question1":"AUS","question2":["item1","item2"]}];
//console.log(arr);
function solve(list){
var map = new Map();
var entry = null;
for(var item of list){
if(!map.has(item.question1))
map.set(item.question1, {question:'question1'});
entry = map.get(item.question1);
if(entry.hasOwnProperty(item.question1))
entry[item.question1] = entry[item.question1] + 1;
else
entry[item.question1] = 1;
if(!map.has(item.question2))
map.set(item.question2, {question: 'question2'});
entry = map.get(item.question2);
if(entry.hasOwnProperty(item.question2))
entry[item.question2] = entry[item.question2] + 1;
else
entry[item.question2] = 1;
}
return Array.from(map.values());
}
console.log(solve(arr))
You could take an object or what ever data structure you like which supports a key/value structure in a nested style and collect first all items and then reder the collected tree.
This approach uses objects, because the keys are strings, this is important for an array as key. This is joint with a comma which is sufficient for this use case.
var data = [{ question1: "USA", question2: ["item1"], question4: 2 }, { question1: "USA", question2: ["item1"], question4: 3 }, { question1: "AUS", question2: ["item1", "item2"] }],
hash = data.reduce((hash, o) => {
Object.entries(o).forEach(([question, value]) => {
var sub = hash[question] = hash[question] || Object.create(null);
sub[value] = sub[value] || { question, [value]: 0 };
sub[value][value]++;
});
return hash;
}, Object.create(null)),
result = Object.values(hash).reduce((r, sub) => [...r, ...Object.values(sub)], []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
First, obtain the countries by using reduce. Then use some nested forEach loops for the rest:
const input = [{"question1":"USA","question2":["item1"],"question4":2},
{"question1":"USA","question2":["item1"],"question4":3},
{"question1":"AUS","question2":["item1","item2"]}];
const countriesOutput = input.reduce((acc, curr) => {
if (!acc.some(e => e[curr.question1])) {
acc.push({ question: "question1", [curr.question1]: 1 });
} else {
acc.find(e => e[curr.question1])[curr.question1]++;
}
return acc;
}, []);
let questionsOutput = [];
input.forEach(item => {
Object.keys(item).forEach(key => {
if (key != "question1") {
if (Array.isArray(item[key])) {
questionsOutput.push({ question: key, [item[key].join(",")]: 1 });
} else {
questionsOutput.push({ question: key, [item[key]]: 1 });
}
}
});
});
const finalOutput = [...countriesOutput, ...questionsOutput];
console.log(finalOutput);
.as-console-wrapper { max-height: 100% !important; top: auto; }
Its a matter of summarizing the input using a dictionary (like Object) and track the duplicates. The "name" of the name/value pair can be uniquely identified by combining the question and answer with some delimiter.
const input = [{
"question1": "USA",
"question2": ["item1"],
"question4": 2
},
{
"question1": "USA",
"question2": ["item1"],
"question4": 3
},
{
"question1": "AUS",
"question2": ["item1", "item2"]
}
];
//Sum the input to an array which we can easily search for duplciates
var repeatCounter = {};
input.forEach(objItem => {
Object.keys(objItem).forEach(propItem => {
//Get the counter and the string
var s = `${propItem}-${objItem[propItem]}`;
var c = repeatCounter[s] || 0;
//Modify it or introduce it if absent
repeatCounter[s] = c + 1;
})
})
var output = Object.keys(repeatCounter).map(element => {
var ret = {'question': element.split('-')[0]}
ret[element.split('-')[1]] = repeatCounter[element];
return ret;
})
console.log(output);
.as-console-wrapper {
max-height: 100% !important;
}
Subtle adjustments such as fortifying the delimiter, converting multiple strings in to array items(as shown in the question) needs to be done on practical grounds.
I have problems with writing a universal function in node that would parse JSON like this:
{
"parserId": 1,
"filters": [
{
"filterName": "replaceTitle",
"regex": "..."
},
{
"filterName": "replaceRegion",
"regex": "..."
}
]}
into multiple JSON like this:
{ "parserId": 1, "filterName": "replaceTitle","regex": "..." },{ "parserId": 1, "filterName": "replaceRegion", "regex": "..."}
It would be great if this function would be universal so doesn't matter what are the names of the fields in JSON, as long as it's build the same way.
Is there any node package already doing it? Thank you for your help!
You could map every item of the array and append an object with parserId with the item of the array.
var object = { parserId: 1, filters: [{ filterName: "replaceTitle", regex: "..." }, { filterName: "replaceRegion", regex: "..." }] },
array = object.filters.map(o => Object.assign({ parserId: object.parserId }, o));
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A more general approach could be the check if a property is an array and loop that array later or add the property to a common object, which keeps the same values for all new generated objects.
Later iterate the arrays and add the content to the objects as well.
This works without specifying the properties who are arrays or just values.
function unnormalize(object) {
var common = {};
return Object
.keys(object)
.filter(k => Array.isArray(object[k]) || (common[k] = object[k], false))
.reduce((r, k) => (object[k].forEach((o, i) => Object.assign(r[i] = r[i] || Object.assign({}, common), o)), r), []);
}
var object = { parserId: 1, filters: [{ filterName: "replaceTitle", regex: "..." }, { filterName: "replaceRegion", regex: "..." }] };
console.log(unnormalize(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use square bracket syntax to provide the name of the array. Then you can use map to transform the array, and Object.assign to merge the objects
let data = {
"parserId": 1,
"filters": [
{
"filterName": "replaceTitle",
"regex": "..."
},
{
"filterName": "replaceRegion",
"regex": "..."
}
]};
function format(data) {
let array = data.filters;
// Remove the array so that it's not duplicated
delete data.filters;
return array.map((item) => Object.assign(item, data));
}
console.log(format(data));
I have an array:
var array = {
"mylist": [
{
"item1": "The Ba",
"id": 1
},
{
"item1": "Hurts Ama",
"id": 2
}
]
}
and to sort them I am using the following function:
function sortByItem(a,b) {
if (a.item1 < b.item1)
return -1;
if (a.item1 > b.item1)
return 1;
return 0;
}
which gives me the output
[Hurts Ama, The Ba]
However, I don't want "The" to be included when comparing, so that the output would actually be:
[Ba, Hurts Ama]
You could replace the at the beginning with following whitespace.
var array = [{ item1: "The Ba", id: 1 }, { item1: "Hurts Ama", id: 2 }, { item1: "Thereafter ", id: 3 }];
array.sort(function (a, b) {
function getStripped(s) { return s.replace(/^the\s+/i, ''); }
return getStripped(a.item1).localeCompare(getStripped(b.item1));
});
console.log(array)
.as-console-wrapper { max-height: 100% !important; top: 0; }
first map a transform function every of your objects that removes any "The " then run your sort by
function transform(item) {
return {
id: item.id,
item: item.replace("The ","")
}
}
var list =
[
{
"item": "The Ba",
"id": 1
},
{
"item": "Hurts Ama",
"id": 2
}
]
list.map(transform).sort(sortByItem)