I have an object A as shown below.
var A = {
"1": [ "1_1", "1_2", "1_3" ],
"2": [ "2_1", "2_2" ]
};
Need to build a new array dynamically using js. Suppose
object A key should map to attribute text of Array AA and value should be to children as given below.
var AA = [
{
"text": "1",
"state": "open",
"children": [
{ "text": "1_1" },
{ "text": "1_2" },
{ "text": "1_3" }
]
},
{
"text": "2",
"state": "open",
"children": [
{ "text": "2_1" },
{ "text": "2_2" }
]
}
];
This is my function but its not working as expected. Could someone pls help?
function constructJSONArr() {
var A = {
"1": [ "1_1", "1_2", "1_3" ],
"2": [ "2_1", "2_2" ]
};
for (var key in A) {
var tempArr = [];
tempArr.push(key);
for (var i = 0; i < key.length; i++) {
return {
'text': key,
'state': 'closed',
'children': A[key].map(function(child) {
return {
'text': child
};
})
}
}
}
}
When you return inside a function, the function ends and returns immediately. In your case, the return inside the for loop causes the function to return the 1st key object. To solve this, you need to create the objects and push them into an arr. You can return freely inside Array.map() because each iteration invokes a function.
Fixed solution:
Iterate with for...in. Get the key. Push a new object into arr. Use the key as the text property, the state, and children. To create the children get the array from the original object by the key, and use Array.map() to generate the child objects. Return arr.
var A = {
"1": ["1_1", "1_2", "1_3"],
"2": ["2_1", "2_2"]
};
function constructJSONArr(A) {
var arr = [];
for (var key in A) {
arr.push({
text: key,
state: 'closed',
children: A[key].map(function(t) {
return {
text: t
};
})
});
}
return arr;
}
var result = constructJSONArr(A);
console.log(result);
ESNext solution
Use Object.entries() to get keys and respective values from the object A. Iterate the entries with two nested Array.map() calls. The 1st to create the outer object, and the 2nd to create the children.
const A = {
"1": ["1_1", "1_2", "1_3"],
"2": ["2_1", "2_2"]
};
const constructJSONArr = (obj) =>
Object.entries(obj).map(([text, children]) => ({
text,
state: 'closed',
children: children.map((text) => ({
text
}))
}));
var result = constructJSONArr(A);
console.log(result);
You can use Object.keys() to iterate through the object and Array.map to create the new array.
var A = {
"1": ["1_1", "1_2", "1_3"],
"2": ["2_1", "2_2"]
};
var transformed = Object.keys(A).map(key => {
return {
text: key,
state: "open",
children: A[key].map(value => {
return {
text: value
};
})
};
});
console.log(transformed);
Related
I have a array as follows:
data = [
{
"id":1
"name":"london"
},
{
"id":2
"name":"paris"
},
{
"id":3
"name":"london"
},
{
"id":4
"name":"paris"
},
{
"id":5
"name":"australia"
},
{
"id":6
"name":"newzearland"
}
]
At runtime this array can have n number of elements. I want to group this array with respect to name attribute. All the elements with same name should be moved to a separate array. I don't know the what value can name have in advance. This is coming at runtime. For example, from above array I want final output as follows:
output:
newArray1 = [
{
"id":1
"name":"london"
},
{
"id":3
"name":"london"
}
]
newArray2 = [
{
"id":2
"name":"paris"
},
{
"id":4
"name":"paris"
}
]
newArray3 = [
{
"id":5
"name":"australia"
}
]
newArray4 = [
{
"id":6
"name":"newzearland"
}
]
How can I do that?
As Teemu has already pointed out in a comment, creating new variables to store the data is not ideal. You would have no way of knowing how many groups you've created and using variables that you can't be sure exist is not the best way to write code. Fortunately, JavaScript has objects, which can store data like this in a much cleaner way. Here's the code I've come up with:
function groupBy(arr, key) {
let res = {}
for (let element of arr) {
if (res.hasOwnProperty(element[key])) {
res[element[key]].push(element)
} else {
res[element[key]] = [element]
}
}
return res
}
This code is not the best, most efficient code ever, but it is written to be easier to understand for someone still learning. This code loops over every element in your data and checks whether our result already contains an array for elements with that name. If there's already an array for elements with that name, the current element is added to it. If there isn't one, a new one is created with the current element inside it. To do exactly what you want, you'd call this function with groupBy(data, "name") and assign it to a new variable like groupedData (THIS DOES NOT MODIFY THE DATA, IT RETURNS A NEW OBJECT OF GROUPED DATA) .
Start by getting all the unique .names, then map them to the original array filtered by each .name:
const data = [{
"id": 1, "name": "london"
},
{
"id": 2, "name": "paris"
},
{
"id": 3, "name": "london"
},
{
"id": 4, "name": "paris"
},
{
"id": 5, "name": "australia"
},
{
"id": 6, "name": "newzearland"
}
];
const newData = [...new Set(data
//Get all names in an array
.map(({name}) => name))]
//For each name filter original array by name
.map(n => data.filter(({name}) => n === name));
console.log( newData );
//OUTPUT: [newArray1, newArray2, .....]
You can get the expected result with grouping by key approach.
const data = [{"id":1,"name":"london"},{"id":2,"name":"paris"},{"id":3,"name":"london"},{"id":4,"name":"paris"},{"id":5,"name":"australia"},{"id":6,"name":"newzearland"}];
const result = Object.values(data.reduce((acc, obj) =>
({ ...acc, [obj.name]: [...(acc[obj.name] ?? []), obj] }), {}));
console.log(result);
const [newArray1, newArray2, newArray3, newArray4, ...rest] = result;
console.log('newArray1:', newArray1);
console.log('newArray2:', newArray2);
console.log('newArray3:', newArray3);
console.log('newArray4:', newArray4);
.as-console-wrapper{min-height: 100%!important; top: 0}
I have a Json data that I want to have in a different format.
My original json data is:
{
"info": {
"file1": {
"book1": {
"lines": {
"102:0": [
"102:0"
],
"105:4": [
"106:4"
],
"106:4": [
"107:1",
"108:1"
]
}
}
}
}
}
And I want to map it as following:
{
"name": "main",
"children": [
{
"name": "file1",
"children": [
{
"name": "book1",
"group": "1",
"lines": [
"102",
"102"
],
[
"105",
"106"
],
[
"106",
"107",
"108"
]
}
],
"group": 1,
}
],
"group": 0
}
But the number of books and number of files will be more. Here in the lines the 1st part (before the :) inside the "" is taken ("106:4" becomes "106"). The number from the key goes 1st and then the number(s) from the value goes and make a list (["106", "107", "108"]). The group information is new and it depends on parent-child information. 1st parent is group 0 and so on. The first name ("main") is also user defined.
I tried the following code so far:
function build(data) {
return Object.entries(data).reduce((r, [key, value], idx) => {
//const obj = {}
const obj = {
name: 'main',
children: [],
group: 0,
lines: []
}
if (key !== 'reduced control flow') {
obj.name = key;
obj.children = build(value)
if(!(key.includes(":")))
obj.group = idx + 1;
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
The group information is not generating correctly. I am trying to figure out that how to get the correct group information. I would really appreciate if you can help me to figure it out.
You could use reduce method and create recursive function to build the nested structure.
const data = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}}
function build(data) {
return Object.entries(data).reduce((r, [key, value]) => {
const obj = {}
if (key !== 'lines') {
obj.name = key;
obj.children = build(value)
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
I couldn't understand the logic behind group property, so you might need to add more info for that, but for the rest, you can try these 2 functions that recursively transform the object into what you are trying to get.
var a = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}};
var transform = function (o) {
return Object.keys(o)
.map((k) => {
return {"name": k, "children": (k === "lines" ? parseLines(o[k]) : transform(o[k])) }
}
)
}
var parseLines = function (lines) {
return Object.keys(lines)
.map(v => [v.split(':')[0], ...(lines[v].map(l => l.split(":")[0]))])
}
console.log(JSON.stringify(transform(a)[0], null, 2));
Consider this data:
{
"id": 1,
"title": "Title one",
"category_data": {
"2": "Team",
"7": "Queries"
}
},
I loop through my data to all the categories using this function:
remove_category_duplicates: function () {
// Get all categories and remove duplicates
let data = {}
this.info.forEach(i=>{
Object.assign(data,i.category_data);
})
return data;
},
This returns an object, like so:
Object {
12: "Fax",
2: "Team",
6: "Questions",
7: "Queries"
}
How can I also return just the value (Ex, Fax)? I want to then push those name values into an array.
Thanks
To return only values you can use the Array.map function like this:
let arr = this.info.map(val => val.category_data);
...or use the newer Object.values() as mentioned in the comments ;)
You can push values by this way
var info = [{
"id": 1,
"title": "Title one",
"category_data": {
"2": "Team",
"7": "Queries"
}
}];
var remove_category_duplicates = function () {
// Get all categories and remove duplicates
let data = [];
for (var i in info) {
if (info[i] && info[i].category_data) {
var category_data=info[i].category_data;
for (var j in category_data) {
data.push(category_data[j]);
}
}
}
console.log(data)
return data;
};
remove_category_duplicates();
I have an array of data. Some of the key in the array are same. I would like to create a new array based on the key and add the other data.
This is my array
var myObjOne = [
{
"name":"John",
"id":1,
"car":"maruti"
},
{
"name":"John",
"id":2,
"car":"wolks"
},
{
"name":"John",
"id":3,
"car":"bmw"
},
{
"name":"Peter",
"id":4,
"car":"alto"
},
{
"name":"Peter",
"id":5,
"car":"swift"
}
];
I would like to convert the array in to the below format.
var myObj = [
{
"name":"John",
"items": [
{ "id":1, "car":"maruti" },
{ "id":2, "car":"wolks" },
{ "id":3, "car":"bmw" }
]},
{
"name":"Peter",
"items": [
{ "id":4, "car":"alto" },
{ "id":5, "car":"swift" },
]
}
];
I am working on a node environment.
You can create an object using Array#reduce first which maps name with items, and then create the final array by looping over the intermediate map using a for...of loop:
var source = [{"name":"John","id":1,"car":"maruti"},{"name":"John","id":2,"car":"wolks"},{"name":"John","id":3,"car":"bmw"},{"name":"Peter","id":4,"cars":"alto"},{"name":"Peter","id":5,"cars":"swift"}];
const map = source.reduce((acc, {name, ...obj}) => {
if (!acc[name]) {
acc[name] = [];
}
acc[name].push(obj);
return acc;
}, {});
const result = [];
for (let[name, items] of Object.entries(map)) {
result.push({name, items});
}
console.log(result);
Array.reduce is at rescue.This method accepts an accumulator and current
item. Check in the accumulator if there exist an object where the value of name property is John or Peter
var myObjOne = [{
"name": "John",
"id": 1,
"car": "maruti"
},
{
"name": "John",
"id": 2,
"car": "wolks"
},
{
"name": "John",
"id": 3,
"car": "bmw"
},
{
"name": "Peter",
"id": 4,
"car": "alto"
},
{
"name": "Peter",
"id": 5,
"car": "swift"
}
];
var newObj = myObjOne.reduce(function(acc, curr, currIndex) {
// using findIndex to check if there exist an object
// where the value of the name property is John, Peter
// if it exist it will return the index else it will return -1
let ifNameExist = acc.findIndex(function(item) {
return item.name === curr.name;
})
// if -1 then create a object with name and item property and push
// it to the accumulator
if (ifNameExist === -1) {
let nameObj = {};
nameObj.name = curr.name;
nameObj.items = [];
nameObj.items.push({
id: curr.id,
car: curr.car
})
acc.push(nameObj)
} else {
// if such an object already exist then just update the item array
acc[ifNameExist].items.push({
id: curr.id,
car: curr.car
})
}
return acc;
}, []);
console.log(newObj)
Use .reduce to group by name, and use .find inside the reducer to find if the matching name has already been added:
const input=[{"name":"John","id":1,"car":"maruti"},{"name":"John","id":2,"car":"wolks"},{"name":"John","id":3,"car":"bmw"},{"name":"Peter","id":4,"cars":"alto"},{"name":"Peter","id":5,"cars":"swift"}]
const output = input.reduce((a, { name, ...item }) => {
const foundNameObj = a.find(nameObj => nameObj.name === name);
if (foundNameObj) foundNameObj.items.push(item);
else a.push({ name, items: [item] });
return a;
}, []);
console.log(output);
I wish to filter a nested javascript object by the value of the "step" key:
var data = {
"name": "Root",
"step": 1,
"id": "0.0",
"children": [
{
"name": "first level child 1",
"id": "0.1",
"step":2,
"children": [
{
"name": "second level child 1",
"id": "0.1.1",
"step": 3,
"children": [
{
"name": "third level child 1",
"id": "0.1.1.1",
"step": 4,
"children": []},
{
"name": "third level child 2",
"id": "0.1.1.2",
"step": 5,
"children": []}
]},
]}
]
};
var subdata = data.children.filter(function (d) {
return (d.step <= 2)});
This just returns the unmodified nested object, even if I put value of filter to 1.
does .filter work on nested objects or do I need to roll my own function here, advise and correct code appreciated.
cjm
Recursive filter functions are fairly easy to create. This is an example, which strips a JS object of all items defined ["depth","x","x0","y","y0","parent","size"]:
function filter(data) {
for(var i in data){
if(["depth","x","x0","y","y0","parent","size"].indexOf(i) != -1){
delete data[i];
} else if (i === "children") {
for (var j in data.children) {
data.children[j] = filter(data.children[j])
}
}
}
return data;
}
If you would like to filter by something else, just updated the 2nd line with your filter function of choice.
Here's the function to filter nested arrays:
const filter = arr => condition => {
const res = [];
for (const item of arr) {
if (condition(item)) {
if (!item.children) {
res.push({ ...item });
} else {
const children = filter(item.children)(condition);
res.push({ ...item, children })
}
}
}
return res;
}
The only thing you have to do is to wrap your root object into an array to reach self-similarity. In common, your input array should look like this:
data = [
{ <...>, children: [
{ <...>, children: [...] },
...
] },
...
]
where <...> stands for some properties (in your case those are "name", "step" and "id"), and "children" is an optional service property.
Now you can pass your wrapped object into the filter function alongside a condition callback:
filter(data)(item => item.step <= 2)
and you'll get your structure filtered.
Here are a few more functions to deal with such structures I've just coded for fun:
const map = arr => f => {
const res = [];
for (const item of arr) {
if (!item.children) {
res.push({ ...f({ ...item }) });
} else {
res.push({ ...f({ ...item }), children: map(item.children)(f) });
}
}
return res;
}
const reduce = arr => g => init => {
if (!arr) return undefined;
let res = init;
for (const item of arr) {
if (!item.children) {
res = g(res)({ ...item });
} else {
res = g(res)({ ...item });
res = reduce(item.children)(g)(res);
}
}
return res;
}
Usage examples:
map(data)(item => ({ step: item.step }))
reduce(data)($ => item => $ + item.step)(0)
Likely, the code samples aren't ideal but probably could push someone to the right direction.
Yes, filter works on one array (list), like the children of one node. You have got a tree, if you want to search the whole tree you will need to use a tree traversal algorithm or you first put all nodes into an array which you can filter. I'm sure you can write the code yourself.