Related
My object list looks like this:
const a = [
{
id: 1,
name: 'NewYork',
children: [],
},
{
id: 2,
name: 'Tokyo',
children: [
{
id: 7,
name: 'Toshima',
children: [],
},
{
id: 8,
name: 'Minato',
children: [
{
id: 17,
name: 'Sugamo',
children: [],
},
{
id: 18,
name: 'Kamiichi',
children: [],
},
],
},
],
},
];
And now I have an id 18, I'd like to make a String like Tokyo>Minato>Kamiichi
How can I make it work in javascript (no third party library) ?
Actually I have a very long nested object.
One way is to use a recursive function for this:
const a = {
companies: [{
id: 1,
name: 'NewYork',
children: [],
},
{
id: 2,
name: 'Tokyo',
children: [{
id: 7,
name: 'Toshima',
children: [],
},
{
id: 8,
name: 'Minato',
children: [{
id: 17,
name: 'Sugamo',
children: [],
},
{
id: 18,
name: 'Kamiichi',
children: [],
},
],
},
],
},
],
};
function getPath(obj, id) {
if (obj.id === id) return obj.name;
for (const child of obj.children) {
const path = getPath(child, id);
if (path) {
return obj.name ? obj.name + '>' + path : path;
}
}
return null;
}
console.log(getPath({
children: a.companies
}, 18));
I have this array of objects:
const arrayOfObjects = [{
id: 10,
children: [1000]
},
{
id: 10,
children: [2000]
},
{
id: 20,
children: [1000]
},
{
id: 20,
children: [1000, 2000]
},
{
id: 20,
children: [2000]
},
];
I want to remove duplicates using this code:
const arrayHashMap = arrayOfObjects.reduce((obj, item) => {
if (obj[item.id]) {
// obj[item.id].children.push(...item.children);
const temporaryArray = [...obj[item.id].children, ...item.children];
obj[item.id].children = [...new Set(temporaryArray)];
} else {
obj[item.id] = {
...item
};
}
return obj;
}, {});
const result = Object.values(arrayHashMap);
In this code I commented part where I push values to array. I tried to use "new Set" to remove duplicates from final array, but I am always assigning the value to "obj[item.id].children". Is this OK or is there a better way to write this?
Expected result:
[{
id: 10,
children: [1000, 2000]
}, {
id: 20,
children: [1000, 2000]
}]
Thanks
You could group by id and check the array if the value not exists, then push the value.
const
data = [{ id: 10, children: [1000] }, { id: 10, children: [2000] }, { id: 20, children: [1000] }, { id: 20, children: [1000, 2000] }, { id: 20, children: [2000] }],
result = Object.values(data.reduce((r, { id, children }) => {
r[id] ??= { id, children: [] };
children.forEach(v => {
if (!r[id].children.includes(v)) r[id].children.push(v);
})
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use Array#prototype#reduce to reduce over the array and initialize a set over the children property and keep on adding to the set and lastly map the set back to an array.
const arrayOfObjects = [{
id: 10,
children: [1000]
},
{
id: 10,
children: [2000]
},
{
id: 20,
children: [1000]
},
{
id: 20,
children: [1000, 2000]
},
{
id: 20,
children: [2000]
},
];
const result = Object.values(
arrayOfObjects.reduce((r, c) => {
r[c.id] = r[c.id] || {
id: c.id,
children: new Set()
};
c.children.forEach((item) => r[c.id].children.add(item));
return r;
}, Object.create(null))
)
.map((x) => ({
id: x.id,
children: [...x.children]
}));
console.log(result);
const arr = [
{
id: 10,
children: [1000],
},
{
id: 10,
children: [2000],
},
{
id: 20,
children: [1000],
},
{
id: 20,
children: [1000, 2000],
},
{
id: 20,
children: [2000],
},
];
let result = arr.reduce((acc, i) => {
let obj = acc.find((a) => a.id === i.id);
obj ? (obj.children = [...new Set(obj.children.concat(i.children))]): acc.push(i);
return acc;
}, []);
console.log(result);
you can try this fiddle : https://jsfiddle.net/d0kboywv/2/
const arrayOfObjects = [
{
id: 10,
children: [1000]
},
{
id: 10,
children: [2000]
},
{
id: 20,
children: [1000]
},
{
id: 20,
children: [1000, 2000, 3000]
},
{
id: 20,
children: [2000, 4000]
},
];
let mappedArray = new Map(arrayOfObjects.map(o => [o.id, {}] ));
for (let obj of arrayOfObjects) {
let child = mappedArray.get(obj.id);
for (let [key, val] of Object.entries(obj.children)) {
child[key] = (child[key] || new Set).add(val);
}
}
let result = Array.from(mappedArray.entries(), ([id, child]) => ({
id,
children: [...new Set(Object.entries(child).map(([k, v]) =>
[...v]
).reduce((a, b) => a.concat(b), []))].sort()
}));
console.log(result);
It do the job for me !
you can temporary transform data structure to more simple
const objectOfArray = {};
your id is key, your children is value
I use name initialData for refer to your array
const objectOfArray = {};
initialData.forEach(e => {
if (objectOfArray[e.id] {
objectOfArray[e.id].push(...e.children);
} else {
objectOfArray[e.id] = [...e.children];
}
});
const result = Object.entries(objectOfArray).map(([id, children]) => {
return {
id,
children: children.filter((e, i) => i === chilren.indexOf(i)),
}
});
You can also achieve expected output by running the below code
makeMapping = {};
for (let obj of arrayOfObjects) {
makeMapping[obj.id] = {...obj, children: [...new Set([...obj.children, ...(makeMapping[obj.id]?.children || [])])]};
}
console.log(Object.values(makeMapping));
i dont know about "better", but perhaps terser:
const arrayOfObjects = [{
id: 10,
children: [1000]
},
{
id: 10,
children: [2000]
},
{
id: 20,
children: [1000]
},
{
id: 20,
children: [1000, 2000]
},
{
id: 20,
children: [2000]
},
];
const arrayHashmap = arrayOfObjects.reduce((obj, {
id,
children
}) => ({
...obj,
[id]: {
id,
children: [...new Set([
...obj[id]?.children ?? [],
...children
])]
}
}), {})
const result = Object.values(arrayHashmap);
console.log(result)
edit: whoops, the "tidy" button changed semantics. fixed.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have the following object, and I want to put all the ids in one array.
let obj = {
_id: 1,
children: [
{
id: 2,
children: [
{
id: 21,
children: [],
},
{
id: 22,
children: [],
},
{
id: 23,
children: [
{
id: 231,
children: [],
}
]
}
]
},
{
id: 3,
children: [
{
id: 31,
children: [],
},
{
id: 32,
children: [],
isCriteria: true
},
{
id: 33,
isCriteria: true
}
]
}
]
}
I want in result:
[1, 2, 21, 22, 23, 231, 3, 31, 32, 33]
PS: the object is set dynamically, so we don't know how deep the children are.
This one is uglier, but also should be lighter on garbage collector because it does not create and spread temporary arrays.
/**
* #param {number[]} arr Target array to which numbers will be appended
* #param {object} obj Source data
* #return {number[]} arr mutated with additional numbers (if any found)
*/
function arrayAppendIdsFrom (arr, obj) {
arr.push(obj.id);
if (Array.isArray(obj.children) && obj.children.length > 0) {
obj.children.forEach(child => arrayAppendIdsFrom(arr, child));
}
return arr;
}
/*
* Example data
*/
let obj = {
id: 1,
children: [
{
id: 2,
children: [
{
id: 21,
children: [],
},
{
id: 22,
children: [],
},
{
id: 23,
children: [
{
id: 231,
children: [],
}
]
}
]
},
{
id: 3,
children: [
{
id: 31,
children: [],
},
{
id: 32,
children: [],
isCriteria: true
},
{
id: 33,
isCriteria: true
}
]
}
]
};
// Example use
console.log(arrayAppendIdsFrom([], obj));
You'll need a recursive iteration. Quick example:
let ids = [];
function getIds(obj) {
// check for id
if (obj.hasOwnProperty('id')) {
ids = [...ids, obj.id];
}
// check for children and do recursive check
if (obj.hasOwnProperty('children') && obj.children.length > 0) {
obj.children.forEach(child => {
getIds(child);
});
}
}
// obj here is your exmple obj with the nested structure
getIds(obj);
console.log(ids);
// [2, 21, 22, 23, 231]
You could use a recursive function like this:
const myObj = obj = {
_id: 1,
children: [
{
id: 2,
children: [
{
id: 21,
children: [],
},
{
id: 22,
children: [],
},
{
id: 23,
children: [
{
id: 231,
children: [],
}
]
}
]
},
{
id: 3,
children: [
{
id: 31,
children: [],
},
{
id: 32,
children: [],
isCriteria: true
},
{
id: 33,
isCriteria: true
}
]
}
]
}
var idArray = []
function func(obj) {
idArray.push(obj._id ? obj._id : obj.id)
if (!obj.children) {
return
}
obj.children.forEach(child => func(child))
}
func(myObj)
console.log(idArray)
You could create a function (fill_ids) which loop every property of obj and push into an array (ids) the values of those with key _id or id, e.g.
let ids = [];
function fill_ids(obj) {
for (i in obj) {
if (i == '_id' || i == 'id') {
ids.push(obj[i]);
} else {
fill_ids(obj[i]);
}
}
}
let obj = {
_id: 1,
children: [{
id: 2,
children: [{
id: 21,
children: [],
}, {
id: 22,
children: [],
}, {
id: 23,
children: [{
id: 231,
children: [],
}]
}]
}, {
id: 3,
children: [{
id: 31,
children: [],
}, {
id: 32,
children: [],
isCriteria: true
}, {
id: 33,
isCriteria: true
}]
}]
};
fill_ids(obj);
console.log(ids);
you may use flatMap
see another example with flatDeep
let obj = {
_id: 1,
children: [
{
id: 2,
children: [
{
id: 21,
children: [],
},
{
id: 22,
children: [],
},
{
id: 23,
children: [
{
id: 231,
children: [],
}
]
}
]
},
{
id: 3,
children: [
{
id: 31,
children: [],
},
{
id: 32,
children: [],
isCriteria: true
},
{
id: 33,
isCriteria: true
}
]
}
]
}
function flatDeep(o) {
return [o.id||o._id].concat( (o.children||[]).flatMap(flatDeep) );
};
console.log(flatDeep(obj));
you could remove the ||o._id and o.children||[] depending on whether your sample input was correct or approximative :)
I try to write mixin for underscore, which can find node by some params, for example:
_.findDeep(tree, {id: 5456, parent_id: 555})
Tree:
var tree = [
{
id: 22,
name: 'qqqq',
depth: 0,
parent_id: 11,
children: [
{
id: 222,
name: 'ttttt',
depth: 1,
parent_id: 444,
children: [],
positions: []
},
{
id: 5456,
name: 'yyyy',
depth: 1,
parent_id: 555,
children: [
{
id: 6767,
name: 'dfgfdg',
depth: 3,
parent_id: 6564,
children: [],
positions: []
},
{
id: 4345,
name: 'dfgdgfg',
depth: 3,
parent_id: 45234,
children: [],
positions: []
},
],
positions: []
},
],
positions: [
{
id: 14,
name: 'rere',
price: 20
},
{
id: 12,
name: 'tttyty',
price: 30
},
]
},
{
id: 33,
name: 'wwww',
depth: 0,
parent_id: 22,
children: [],
positions: []
},
{
id: 44,
name: 'eeee',
depth: 0,
parent_id: 33,
children: [],
positions: []
},
]
Wrong function, which alaways returns 'undefined', but console.log display founded node:
_.mixin({
findDeep: function(items, attrs) {
var key, n_key, n_value, result, value;
result = _.findWhere(items, attrs);
console.log(items, result, _.isUndefined(result));
if (_.isUndefined(result)) {
for (key in items) {
value = items[key];
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
result = _.findDeep(n_value, attrs);
if (!_.isUndefined(result)) {
return result;
}
}
}
}
}
return result;
}
});
where is mistake? please help me
In your code, your
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
_.findDeep(n_value, attrs);
}
}
is doing a deep search, but doesn't return any result. You should assign the result to the search, and if the result is not undefined, return it or break the for loop imediately.
So it becomes:
_.mixin({
findDeep: function(items, attrs) {
var key, n_key, n_value, result, value;
result = _.findWhere(items, attrs);
console.log(items, result, _.isUndefined(result));
if (_.isUndefined(result)) {
for (key in items) {
value = items[key];
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
result = _.findDeep(n_value, attrs);
}
// Once you find the result, you can return the founded result
if (!_.isUndefined(result)) {
return result;
}
}
}
}
return result;
}
});
Snippet to show the correct result:
var tree = [
{
id: 22,
name: 'qqqq',
depth: 0,
parent_id: 11,
children: [
{
id: 222,
name: 'ttttt',
depth: 1,
parent_id: 444,
children: [],
positions: []
},
{
id: 5456,
name: 'yyyy',
depth: 1,
parent_id: 555,
children: [
{
id: 6767,
name: 'dfgfdg',
depth: 3,
parent_id: 6564,
children: [],
positions: []
},
{
id: 4345,
name: 'dfgdgfg',
depth: 3,
parent_id: 45234,
children: [],
positions: []
},
],
positions: []
},
],
positions: [
{
id: 14,
name: 'rere',
price: 20
},
{
id: 12,
name: 'tttyty',
price: 30
},
]
},
{
id: 33,
name: 'wwww',
depth: 0,
parent_id: 22,
children: [],
positions: []
},
{
id: 44,
name: 'eeee',
depth: 0,
parent_id: 33,
children: [],
positions: []
},
];
_.mixin({
findDeep: function(items, attrs) {
var key, n_key, n_value, result, value;
result = _.findWhere(items, attrs);
console.log(items, result, _.isUndefined(result));
if (_.isUndefined(result)) {
for (key in items) {
value = items[key];
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
result = _.findDeep(n_value, attrs);
}
// Once you find the result, you can return the founded result
if (!_.isUndefined(result)) {
return result;
}
}
}
}
return result;
}
});
console.log(_.findDeep(tree, {id: 5456, parent_id: 555}));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
I have a problem with finding object in nested json! I need to do operations like 'add' to object and 'delete' object in that nested json. Would it be easy to get object by using "JSON.stringify" and in that string find objects ID parameter (every object has its own unique ID). Then from that point find its "wrapper" curly braces ({}) i could get object it self and then delete it or add new object in it.
I had this idea, but have no idea how to select its curly braces... I think it might work, but what do you thing? :)
Here would be the example object! https://jsfiddle.net/gb8hb8g7/
var aa = [
{name: "aaa",
id: 1,
items: [
{name: "bbb",
id: 15,
items: [
{name: "ccc",
id: 44},
{name: "ddd",
id: 91}
]},
{name: "eee",
id: 12}
]
}
];
console.log(JSON.stringify(aa));
You can traverse the nested JSON recursively, to perform the operations you need.
var aa = [
{name: "aaa",
id: 1,
items: [
{name: "bbb",
id: 15,
items: [
{name: "ccc",
id: 44},
{name: "ddd",
id: 91}
]},
{name: "eee",
id: 12}
]
}
];
var fff = {name: "fff", id: 13};
addObj(aa, 91, fff); // Add obj to same array as item 91
chgObj(aa, 91, '^', 'name', 'zzz'); // Change 'name' property of item 91
chgObj(aa, 91, '+', 'other', 'test'); // Add property to item 91
chgObj(aa, 91, '+', 'gone', 'delete me'); // Add property to item 91
chgObj(aa, 91, '-', 'gone'); // Delete property from item 91
dltObj(aa, 44); // Delete item 44
function addObj(itemArr, nId, newObj) {
for (var i = 0; i < itemArr.length; i++) {
if (itemArr[i].id && itemArr[i].id === nId) {
itemArr.push(newObj);
} else {
if (itemArr[i].items) {
addObj(itemArr[i].items, nId, newObj);
}
}
}
}
function chgObj(itemArr, nId, operator, prop, val) {
for (var i = 0; i < itemArr.length; i++) {
if (itemArr[i].id && itemArr[i].id === nId) {
switch (operator) {
case '+':
if (!itemArr[i][prop]) {
itemArr[i][prop] = val;
}
break;
case '-':
if (itemArr[i][prop]) {
delete itemArr[i][prop];
}
break;
case '^':
if (itemArr[i][prop]) {
itemArr[i][prop] = val;
}
break;
}
} else {
if (itemArr[i].items) {
chgObj(itemArr[i].items, nId, operator, prop, val);
}
}
}
}
function dltObj(itemArr, nId) {
for (var i = 0; i < itemArr.length; i++) {
if (itemArr[i].id && itemArr[i].id === nId) {
itemArr.splice(i, 1);
} else {
if (itemArr[i].items) {
dltObj(itemArr[i].items, nId);
}
}
}
}
alert(JSON.stringify(aa));
new fiddle: https://jsfiddle.net/ta4pjqew/2
You should be able to just use your objects like you are traversing a big array:
var aa = [
{name: "aaa",
id: 1,
items: [
{name: "bbb",
id: 15,
items: [
{name: "ccc",
id: 44},
{name: "ddd",
id: 91}
]},
{name: "eee",
id: 12}
]
}
];
aa[0].name = 'abc';
aa[0].newprop = 23;
console.log(aa[0].items[0].items[1]);
delete aa[0].items[0].items[1];
console.log(aa[0].items[0].items[1]);
console.log(JSON.stringify(aa));
Take a look at object-scan. Makes it every easy to write clean and maintainable code to modify complex data structures. Here is how one could answer your question.
// const objectScan = require('object-scan');
const tool = (() => {
const scanner = objectScan(['**.items[*]'], {
abort: true,
rtn: 'bool',
filterFn: ({ value, parent, property, context }) => {
if (value.id === context.id) {
context.fn({ value, parent, property });
return true;
}
return false;
}
});
return {
add: (data, id, obj) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property + 1, 0, obj) }),
del: (data, id) => scanner(data, { id, fn: ({ parent, property }) => parent.splice(property, 1) }),
mod: (data, id, prop, v = undefined) => scanner(data, {
id,
fn: ({ value }) => {
if (value !== undefined) {
value[prop] = v;
} else {
delete value[prop];
}
}
})
};
})();
// -------------------------------
const aa = [{ name: 'aaa', id: 1, items: [ { name: 'bbb', id: 15, items: [ { name: 'ccc', id: 44 }, { name: 'ddd', id: 91 } ] }, { name: 'eee', id: 12 } ] }];
const fff = { name: 'fff', id: 13 };
const exec = (fn) => {
console.log('---------------');
console.log(fn.toString());
console.log(fn());
console.log(aa);
};
exec(() => tool.add(aa, 91, fff)); // Add obj to array after item 91
exec(() => tool.mod(aa, 91, 'name', 'zzz')); // Change 'name' property of item 91
exec(() => tool.mod(aa, 91, 'other', 'test')); // Add property to item 91
exec(() => tool.mod(aa, 91, 'gone', 'delete me')); // Add property to item 91
exec(() => tool.mod(aa, 91, 'gone')); // Delete property from item 91
exec(() => tool.del(aa, 44)); // Delete item 44
// => ---------------
// => () => tool.add(aa, 91, fff)
// => true
// => [ { name: 'aaa', id: 1, items: [ { name: 'bbb', id: 15, items: [ { name: 'ccc', id: 44 }, { name: 'ddd', id: 91 }, { name: 'fff', id: 13 } ] }, { name: 'eee', id: 12 } ] } ]
// => ---------------
// => () => tool.mod(aa, 91, 'name', 'zzz')
// => true
// => [ { name: 'aaa', id: 1, items: [ { name: 'bbb', id: 15, items: [ { name: 'ccc', id: 44 }, { name: 'zzz', id: 91 }, { name: 'fff', id: 13 } ] }, { name: 'eee', id: 12 } ] } ]
// => ---------------
// => () => tool.mod(aa, 91, 'other', 'test')
// => true
// => [ { name: 'aaa', id: 1, items: [ { name: 'bbb', id: 15, items: [ { name: 'ccc', id: 44 }, { name: 'zzz', id: 91, other: 'test' }, { name: 'fff', id: 13 } ] }, { name: 'eee', id: 12 } ] } ]
// => ---------------
// => () => tool.mod(aa, 91, 'gone', 'delete me')
// => true
// => [ { name: 'aaa', id: 1, items: [ { name: 'bbb', id: 15, items: [ { name: 'ccc', id: 44 }, { name: 'zzz', id: 91, other: 'test', gone: 'delete me' }, { name: 'fff', id: 13 } ] }, { name: 'eee', id: 12 } ] } ]
// => ---------------
// => () => tool.mod(aa, 91, 'gone')
// => true
// => [ { name: 'aaa', id: 1, items: [ { name: 'bbb', id: 15, items: [ { name: 'ccc', id: 44 }, { name: 'zzz', id: 91, other: 'test', gone: undefined }, { name: 'fff', id: 13 } ] }, { name: 'eee', id: 12 } ] } ]
// => ---------------
// => () => tool.del(aa, 44)
// => true
// => [ { name: 'aaa', id: 1, items: [ { name: 'bbb', id: 15, items: [ { name: 'zzz', id: 91, other: 'test', gone: undefined }, { name: 'fff', id: 13 } ] }, { name: 'eee', id: 12 } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan