Suppose I have a nested object list like this:
[{
"id": "a",
"name": "Object a",
"parentId": "root",
"children": [{
"id": "c",
"name": "Object c"
},{
"id": "d",
"name": "Object D",
"parentId": "a"
}]
}, {
"id": "b",
"name": "Object b",
"parentId": "root"
}]
The object anatomy is simple: id, name, children (if any) and parentId. I'm using this flatten function that turns the nested object
into a flat array:
function flatten(array) {
var result = [];
array.forEach(function (a) {
result.push(a);
if (Array.isArray(a.children)) {
result = result.concat(flatten(a.children));
}
});
return result;
}
The thing is that the parentId value is not always persistent for every object, and therefore when the object gets flattened into an array, it's possible to lose a parent & child object relationship.
I need the flatten method to rebuild the parentId value according to the object structure. And there's only one catch, if the object is not a child, then it should have a parentId of root.
Help is very much appreciated
If I follow correctly, you just want to flatten your structure into an array that maintains the parentId relationship inherent in the original tree. If so, then I believe this will do:
const flatten = (xs, parentId = 'root') =>
xs .flatMap (({children = [], id, ...rest}) => [
{id, ...rest, parentId},
... flatten (children, id)
])
const input = [{id: "a", name: "Object a", parentId: "root", children: [{id: "c", name: "Object c"},{id: "d", name: "Object D", parentId: "a"}]}, {id: "b", name: "Object b", parentId: "root"}]
console .log (flatten (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
It's a fairly simple recursion, using flatMap to combine the records and parameter destructuring (with default a parameter for children) to simplify our object handling.
Sounds like you just need to add the parentId to the objects that don't have them. Change
result = result.concat(flatten(a.children));
to
result = result.concat(flatten(a.children.map(addParent(a.parentId)));
const addParent = parentId => item => ({ ...item, parentId });
where the root is called with a parentId of root.
You can pass the parentId in as the second argument to your flatten function like so, with the default parent just being "root".
const arr = [{
"id": "a",
"name": "Object a",
"parentId": "root",
"children": [{
"id": "c",
"name": "Object c"
},{
"id": "d",
"name": "Object D",
"parentId": "a"
}]
}, {
"id": "b",
"name": "Object b",
"parentId": "root"
}]
function flatten(array, parentId = "root") {
let result = [];
array.forEach(function(a) {
a.parentId = parentId;
result.push(a);
if (Array.isArray(a.children)) {
result = result.concat(flatten(a.children, a.id));
}
delete a.children; // optional
});
return result;
}
console.log(flatten(arr));
Related
I am trying to avoid importing lodash for a simple filter. I am trying to do something similar to this...
// Items
[
{ "title": "a", "name": "a", "location": "here"},
{ "title": "b", "name": "a", "location": "here"},
{ "title": "d", "location": "there"},
]
// Filter
{ "name" : "a", "location": "here" }
Should return the first two items. In lodash this is done by something similar to _.filter(Items, Filter). I would like to implement this without lodash or a bunch of code and getting Object.keys. Is there a simple way in modern Javascript to handle this?
Here's what I came up with, seems to work well
let foo = [
{ "title": "a", "name": "a", "location": "here"},
{ "title": "b", "name": "a", "location": "here"},
{ "title": "d", "location": "there"},
];
let bar = { "name" : "a", "location": "here" };
let result = foo.filter(obj =>
Object.entries(bar).every(([k,v]) => obj[k]===v)
);
console.log(result);
I don't believe there's a "simple way in modern Javascript to handle this" but if you don't have a hard requirement on avoiding Object.keys you could write a helper function to do this:
const myItems = [
{ "title": "a", "name": "a", "location": "here"},
{ "title": "b", "name": "a", "location": "here"},
{ "title": "d", "location": "there"},
]
const myFilter = { "name" : "a", "location": "here" };
const filter = (items, filterBy) =>
items.filter(item =>
Object.keys(filterBy).every(key => item[key] === filterBy[key])
)
alert(JSON.stringify(filter(myItems, myFilter)));
You could get the entries of the filter in advance to prevent getting th entries for every filtering iteration.
const
filter = (array, filter) => {
const
filterBy = entries => o => entries.every(([k, v]) => o[k] === v);
return items.filter(filterBy(Object.entries(filters)));
},
items = [{ title: "a", name: "a", location: "here" }, { title: "b", name: "a", location: "here" }, { title: "d", location: "there"}],
filters = { name: "a", location: "here" },
result = filter(items, filters);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I am fairly new to Vue and JS but I am making API calls and getting a JSON response then sending the response to an empty array. How do I get the ID of each object in the array?
The array that the response is being pushed to is structured like this
groups: [
{
"id": "0",
"name": "a",
"price": 5
},
{
"id": "1",
"name": "b",
"price": 5
},
{
"id": "2",
"name": "c",
"price": 5
}
]
I'd like to pull the Id of each object and push the values to an empty array
for(var group in this.groups) {
if (this.groups.hasOwnProperty(0)) {
this.group = this.groups[0];
this.groupsId.push(this.innerObj);
}
}
The error I'm getting is saying Cannot read property '0' of undefined at eval
Ideally I'd like an array that has all the Ids of each object.
this.groups.hasOwnProperty(0) should be group.hasOwnProperty('id')
Use Array.prototype.map() to iterate over an array of objects and collect every ID into a new array:
const res = {
groups: [{
"id": "0",
"name": "a",
"price": 5
},
{
"id": "1",
"name": "b",
"price": 5
},
{
"id": "2",
"name": "c",
"price": 5
}
]
};
const ids = res.groups.map(obj => { // you use this.groups
if(obj.hasOwnProperty('id')) return obj.id;
});
console.log(ids)
There is the Array.map() method:
this.groupsId = this.groups.map(i => i.id);
If you already have elements in this.groupsId you can append the ids using Array.concat():
this.groupsId = this.groupsId.concat(this.groups.map(i => i.id));
You can use Array.prototype.reduce to loop and check if there's id.
const groups = [
{"name": "a","price": 5},
{"id": "1","name": "b","price": 5},
{ "id": "2","name": "c","price": 5}
];
const list = groups.reduce((groupIds, group) => group.id ? [...groupIds, group.id] : groupIds, []);
console.log(list);
Today I was working on a problem, which states as follows:
Problem:
INPUT: [{..}, {..}, ..] Array of objects;
Each object is has {"id": required, "children": []}
The objects has parent-child relation based on "id" and "children" props
OUTPUT: [{..}, {..}, ..] Array in a tree (hierarchy) order :multi-level.
Input:
[{
"id": 1,
"name": "Earth",
"children": [2, 3]
}, {
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}, {
"id": 4,
"name": "Germany",
"children": [5]
}, {
"id": 5,
"name": "Hamburg",
"children": []
}]
OutPut
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [{
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
}]
}]
My approach
I decided to solve this by iterating through each element in the array and recursively find and append objects to children of each element.
So just to start with, I decided to have only First level children appended their respective parents. And my code is following.
var posts = [{"id":1,"name":"Earth","children":[2,3]},{"id":2,"name":"Asia","children":[]},{"id":3,"name":"Europe","children":[4]},{"id":4,"name":"Germany","children":[5]},{"id":5,"name":"Hamburg","children":[]}]
function getElementById (id, posts) {
for(var i =0; i< posts.length; i++){
if(posts[i].id === id){
var found = posts[i];
///// FUN here -> //// posts.splice(i, 1);
return found;
}
}
}
function refactorChildren(element, posts) {
if(!element.children || element.children.length === 0) {
return element;
}
var children = [];
for(var i = 0; i < element.children.length; i++){
var childElement = getElementById(element.children[i], posts);
children.push(childElement);
}
element.children = children;
return element;
}
function iterate(posts) {
var newPosts = [];
var des = [...posts]
for(var i = 0; i < des.length; i++){
var childedElement = refactorChildren(des[i], des);
newPosts.push(childedElement);
}
return newPosts;
}
var filtered = iterate(posts);
console.log(JSON.stringify(filtered))
Surprisingly above code Solves the ACTUAL PROBLEM (except a lil bit of more work)
My Expected Result should be the following: Array of objects with only First level children
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}]
}, {
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
And I do get the above result if I uncomment the ///// FUN here -> //// line. Which is erasing the iterating object on the go.
So my problem is
I want to know - HOW DID? All the objects got appended correctly to their respective Parent objects by that code? My next step was to add a recursion call to the function refactorChildren(with-childElement).
AND
How did, just by adding posts.splice(i, 1); got me MY expected result from the code?
Please help me understand, I just cant go ahead without knowing "HOW".
Thanks
While traversing the objects, you recursively call a function on all its chilfren and remove the objects from the array:
[
{ id: 1, children: [2], }, // < iterator
{ id: 2, children: [] }, // < gets spliced out recursively
]
If a child is in the array before its parent however, this won't work as you copy the child into another array before the parent gets visited.
Maybe you are interested in a different approach with only a single loop for getting the parent elements and their children.
This works for unsorted data, too.
var data = [{ id: 1, name: "Earth", children: [2, 3] }, { id: 2, name: "Asia", children: [] }, { id: 3, name: "Europe", children: [4] }, { id: 4, name: "Germany", children: [5] }, { id: 5, name: "Hamburg", children: [] }],
tree = function (array) {
var r = {},
children = new Set,
result = [];
array.forEach(o => {
Object.assign(
r[o.id] = r[o.id] || {},
o,
{ children: o.children.map(id => (children.add(id), r[id] = r[id] || {})) }
);
});
return Object.values(r).filter(({ id }) => !children.has(id));
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Considering below object array:
[
{
"guid": "j5Dc9Z",
"courses": [
{
"id": 1,
"name": "foo",
}
]
},
{
"guid": "a5gdfS",
"courses": [
{
"id": 2,
"name": "bar",
},
{
"id": 3,
"name": "foo",
},
]
},
{
"guid": "jHab6i",
"courses": [
{
"id": 4,
"name": "foobar",
}
]
},
{...}
]
I am trying to filter an object array, comparing IDs in the nested courses array with in the below array:
filter.courses = [1,3]
The following line works for the nth value in the array: (via https://stackoverflow.com/a/41347441/9766768)
let fil = filter(this.results, { courses: [{ id: this.filter.courses[n] }]});
However, I'm hoping to achieve this (pseudo code below):
let fil = filter(this.results, { courses: [{ id: this.filter.courses }]});
Expected output would be an array of objects containing any of the course IDs elements, in this case:
[
{
"guid": "j5Dc9Z",
"courses": [
{
"id": 1,
"name": "foo",
}
]
},
{
"guid": "a5gdfS",
"courses": [
{
"id": 2,
"name": "bar",
},
{
"id": 3,
"name": "foo",
},
]
}
]
What would be considered the best solution in this case? Avoiding loops would be a bonus.
If you're trying to filter the elements whose course IDs contain in the filter.courses, you may use Array#every and Array#includes to do that:
const data = [{"guid":"j5Dc9Z","courses":[{"id":3,"name":"foo"}]},{"guid":"a5gdfS","courses":[{"id":1,"name":"bar"},{"id":3,"name":"foo"}]},{"guid":"jHab6i","courses":[{"id":7,"name":"foobar"}]}];
const courses = [1, 6, 3];
const r = data.filter(d => d.courses.every(c => courses.includes(c.id)));
console.log(r);
try this,
results = [
{
"guid": "j5Dc9Z",
"courses": [
{
"id": 3,
"name": "foo",
}
]
},
{
"guid": "a5gdfS",
"courses": [
{
"id": 1,
"name": "bar",
},
{
"id": 3,
"name": "foo",
},
]
}
]
var filter = [1]
console.log(results.map(result=>{
result.courses = result.courses.filter(course=>(filter.includes(course.id)))
return result
}))
Explore my recursive solution there: Playground Link
With this solution can nested array of objects being filtered from top level to botton level, layer by layer.
From what I understand the resulting array should contain all objects, that contain at least one course with an id that is contained in the array we use to filter.
So if an object exists with 2 courses - and one of them has an id we are looking for this object should then be part of the array that gets returned (see object with property "guid" : "a5gdfS" in the questions example)
With one little tweak the code provided in the answer by 31piy (marked as best by question owner) will do exactly what we desire. To do so we just change the array method every() to the array method some().
const r = data.filter(d => d.courses.every(c => courses.includes(c.id)));
const r = data.filter(d => d.courses.some(c => courses.includes(c.id)));
With the method every() the resulting array will only contain the objects, where each and every course has an id we are looking for. ("guid": "a5gdfS" is not in the resulting array)
With the method some() the resulting array will contain the objects, where at least one course has an id we are looking for ("guid": "a5gdfS" is in the resulting array)
/* arrays are the same as provided in the question
so that we can check against expected/desired output in the question */
const data = [{
"guid": "j5Dc9Z",
"courses": [{
"id": 1,
"name": "foo",
}]
},
{
"guid": "a5gdfS",
"courses": [{
"id": 2,
"name": "bar",
},
{
"id": 3,
"name": "foo",
},
]
},
{
"guid": "jHab6i",
"courses": [{
"id": 4,
"name": "foobar",
}]
}
];
const courses = [1, 3];
//array contains all objects with at least 1 course that has an id we are looking for
const r = data.filter(d => d.courses.some(c => courses.includes(c.id)));
console.log("code with some " + JSON.stringify(r));
//array contains only objects, whose classes all have an id we are looking for
const o = data.filter(d => d.courses.every(c => courses.includes(c.id)));
console.log("code with every " + JSON.stringify(o));
depending on what we are trying to do either every() or some() might be correct - depends on what we are trying to achieve
I got a json object like this
{
"id": 1,
"name": "A",
"nodes": [
{
"id": 2,
"name": "B",
"nodes": [
{
"id": 3,
"name": "C",
"nodes": []
}
]
}
]
}
If I have as input the id of the object, lets take id: 3, how would I scan the whole three find the object with specific id and then scan upwards to the last parent.
So after the scan is done I know that C has parent B and B has parent A, so I can then print that like A-B-C
all based on me knowing the id of object I want to find parents of.
The above object can be of any lenght and can have many nodes and levels. So anyone has any idea how to traverse up the levels to top level if starting at specific level?
edit:
when I try to parse this
let data = [
{
"id": 1,
"name": "name",
"testing": "something",
"nodes": [
{
"id": 11,
"name": "name",
"testing": "something",
"nodes": []
}
]
},
{
"id": 2,
"name": "name",
"testing": "something",
"nodes": []
}
]
to json object by doing JSON.parse(data) I get an error
SyntaxError: Unexpected token o in JSON at position 1
at JSON.parse (<anonymous>)
Also tried this
let jsonObject = JSON.stringify($scope.data);
jsonObject = JSON.parse(jsonObject);
createTree(jsonObject, null, nodeData.id)
and get different error:
TypeError: obj.nodes is not iterable
Do a basic DFS scan, add parent property along the way, and climb up when node found.
let jsonParsed = JSON.parse(`
{
"id": 1,
"name": "A",
"nodes": [
{
"id": 2,
"name": "B",
"nodes": [
{
"id": 3,
"name": "C",
"nodes": []
}
]
}
]
}
`)
let arr = []
function climbTree(obj) {
arr.unshift(obj.name)
if (obj.parent) {
climbTree(obj.parent)
}
}
function createTree(obj, parent = null, targetId = null) {
obj.parent = parent
if (targetId === obj.id) {
return climbTree(obj)
}
for (let node of obj.nodes) {
createTree(node, obj, targetId)
}
}
createTree(jsonParsed, null, 3)
console.log(arr.join('-'))