Find / delete / add / update objects in nested json - javascript

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

Related

How to change object value in array with objects with if/else in JS

I have an array with objects. I need to find item with current name and change it.
const example = [
{
id: '1234',
desc: 'sample1',
items: [
itemsName: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' }
],
id: 888,
]
},
{
id: '3456',
desc: 'sample2',
items: [
itemsName: [
{ id: 1, name: 'name2' },
{ id: 2, name: 'testItem3' }
],
id: 889,
]
},
I try to do in this way, but it's not working.
I get construction like (5) 
[Array(1), Array(1)]
instead of 
[{…}, {…}]
const findName = (name, changedName) => {
const result = example?.map((group) =>
group.items.map((group) =>
group.itemsName?.map((i) => {
if (i.name === name) return i.name === changedName;
return null;
})
)
);
}
findName('name1', 'name2')
let findName1 = (name, changedName) => {
const result = example?.map((group) =>
group.items.map((group) =>
group.itemsName?.map((i) => {
if (i.name === name) return i.name = changedName;
return null;
})
)
);
}
This will work with following object (your object declaration seems to be wrong)
const example = [
{
id: '1234',
desc: 'sample1',
items: [
{itemsName: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' }
],
id: 888,}
]
},
{
id: '3456',
desc: 'sample2',
items: [
{itemsName: [
{ id: 1, name: 'name2' },
{ id: 2, name: 'testItem3' }
],
id: 889,}
]
}]

move one specific object prop from one array to another array of objects, if condition (JavaScript ES6)

I have 2 arrays of objects returning from 2 different fetch
const result1 = [
{
name: 'matteo',
age: 20,
id: 1,
},
{
name: 'luca',
age: 24,
id: 2,
},
];
const result2 = [
{
warnings: 'yes',
hobby: "tennis",
id: 1,
},
{
warnings: 'many',
hobby: "ping pong",
id: 2,
},
];
This is my current approach but it will merge the entire object from result2 to result1 if they have the same id
const t = result2.reduce((acc, curr) => {
acc[curr.id] = curr;
return acc;
}, {});
const d = result1.map((d) =>
Object.assign(d, t[d.id])
);
The current result is:
{
name: 'matteo',
age: 20,
id: 1,
warnings: "yes",
hobby: "tennis"
},
{
name: 'luca',
age: 24,
id: 2,
warnings: "many",
hobby: "ping pong"
},
I would like to move only the warnings prop from the second array of objects into the first array of objects where the id of object is equal
Desired output:
const result3 = [
{
name: 'matteo',
age: 20,
id: 1,
warnings: "yes"
},
{
name: 'luca',
age: 24,
id: 2,
warnings: "many"
},
];
You can use map to create a new array, and find to get any warning with a matching id:
let result1 = [
{ id: 1, name: 'a', age: 1 },
{ id: 2, name: 'b', age: 2 },
{ id: 3, name: 'c', age: 3 },
{ id: 4, name: 'd', age: 4 }
];
let result2 = [
{ id: 1, hobby: 'aa', warnings: 'aaa' },
{ id: 2, hobby: 'bb', warnings: 'bbb' },
{ id: 4, hobby: 'dd', warnings: 'ddd' }
];
let includeWarnings = (data, warningsArr) => data.map(obj => {
// `w` will be undefined if no matching warning is found
let w = warningsArr.find(warn => warn.id === obj.id);
// Return all the data in `obj`, and the "warnings" property
// of `w` if `w` is defined
return { ...obj, ...(w ? { warnings: w.warnings } : {}) };
});
console.log(includeWarnings(result1, result2));
Note you'd potentially be better off if your data format was structured with id mappings in mind:
let result1 = {
id1: { name: 'name1', age: 1 },
id2: { name: 'name2', age: 2 },
.
.
.
}
let result2 = {
id1: { hobby: 'hobby1', warnings: 'warnings1' },
id2: { hobby: 'hobby2', warnings: 'warnings2' },
.
.
.
}

filter inside forEach does no work properly when finds double objects

I try to get each object of an array and compare it to the objects of another array. If they match, remove the object from the second array.
Strange thing is that if an object is found two times in an array, that object is not filtered.
I want to compare newdata to existing. If an object of newdata has the same id and cat, it will not be in the new array.
existing is
var existing = [{
name: "John",
values_: {
id: 5,
cat: true
}
},
{name: "Jake",
values_: {
id: 3,
cat: true
}
},
{
name: "Alice",
values_: {
id: 2,
cat: false
}
}
];
newdata is
var newdata = [{
name: "Mike",
properties: {
id: 1,
cat: true
}
},
{name: "Jake",
properties: {
id: 3,
cat: true
}
},
{name: "Jake",
properties: {
id: 3,
cat: true
}
},
{
name: "Alice",
properties: {
id: 2,
cat: false
}
}
];
and my filter is
existing.forEach((existingitem, existingindex, existingfeatures) => {
newdata2 = newdata.filter(item => (
existingitem.values_.id != item.properties.id &&
existingitem.values_.cat != item.properties.cat
));
});
console.log('newdata2 - ',newdata2);
The logic thing is for newdata2 to have only Mike . The problem is that I can see Jake two times. Jake should not be there, its already in existing.
If I edit newdata like so (no doubles)
var newdata = [{
name: "Mike",
properties: {
id: 1,
cat: true
}
},
{name: "Jake",
properties: {
id: 3,
cat: true
}
} ,
{
name: "Alice",
properties: {
id: 2,
cat: false
}
}
];
I still can see Jake in newdata2. But why?
Please help me fix this. Is it my filter or the way filter works? Based on the criteria, I should only get Mike at the end. Please advice.
Thank you
var existing = [{
name: "John",
values_: {
id: 5,
cat: true
}
},
{name: "Jake",
values_: {
id: 3,
cat: true
}
},
{
name: "Alice",
values_: {
id: 2,
cat: false
}
}
];
var newdata = [{
name: "Mike",
properties: {
id: 1,
cat: true
}
},
{name: "Jake",
properties: {
id: 3,
cat: true
}
},
{name: "Jake",
properties: {
id: 3,
cat: true
}
},
{
name: "Alice",
properties: {
id: 2,
cat: false
}
}
];
existing.forEach((existingitem, existingindex, existingfeatures) => {
newdata2 = newdata.filter(item => (
existingitem.values_.id != item.properties.id &&
existingitem.values_.cat != item.properties.cat
));
});
console.log('newdata2 - ',newdata2);
... think about a filter (outer) and every (inner) based approach instead of forEach and filter - maybe that makes it easier to think about a correct implementation.
var existingItemList = [{ name: "John", values_: { id: 5, cat: true }}, { name: "Jake", values_: { id: 3, cat: true }}, { name: "Alice", values_: { id: 2, cat: false }}];
var newItemList = [{ name: "Mike", properties: { id: 1, cat: true }}, { name: "Jake", properties: { id: 3, cat: true }}, { name: "Jake", properties: { id: 3, cat: true }}, { name: "Alice", properties: { id: 2, cat: false }}];
var itemList = newItemList.filter(function (newItem) { // filter `newItem` only
return existingItemList.every(function (existingItem) { // if it does not exist
return ( // in `existingItemList`.
//(newItem.name !== existingItem.name) &&
(newItem.properties.id !== existingItem.values_.id)
);
});
});
console.log('itemList : ', itemList);
.as-console-wrapper { max-height: 100%!important; top: 0; }
EDIT
follow up, referring this comment of mine ...
your comparison condition just does not fit what you are really searching/looking for.
If one still assumes that the OP wants to filter a new item only if it does not already exist in another list that it is going to be compared to ...
... one has to write a matcher function that maps and compares item fields in one and the same time.
This comparator/matcher then has to be used in a way that it will filter only the very new item that does not equal any other already existing item.
This can be achieved by a slight change to the former approach from above ...
function doesExistingItemMatchBoundNewItem(existingItem) {
var newItem = this;
return (
(newItem.properties.id === existingItem.values_.id)
&& (newItem.properties.cat === existingItem.values_.cat)
);
}
var existingItemList = [{ name: "John", values_: { id: 5, cat: true }}, { name: "Jake", values_: { id: 3, cat: true }}, { name: "Alice", values_: { id: 2, cat: false }}];
var newItemList = [{ name: "Mike", properties: { id: 1, cat: true }}, { name: "Jake", properties: { id: 3, cat: true }}, { name: "Jake", properties: { id: 3, cat: true }}, { name: "Alice", properties: { id: 2, cat: false }}];
var itemList = newItemList.filter(function (newItem) {
return !existingItemList.some(doesExistingItemMatchBoundNewItem.bind(newItem));
});
console.log('itemList : ', itemList);
.as-console-wrapper { max-height: 100%!important; top: 0; }
You could take a Set and filter the known items.
var existing = [{ name: "John", values_: { id: 5, cat: true } }, { name: "Jake", values_: { id: 3, cat: true } }, { name: "Alice", values_: { id: 2, cat: false } }],
newdata = [{ name: "Mike", properties: { id: 1, cat: true } }, { name: "Jake", properties: { id: 3, cat: true } }, { name: "Jake", properties: { id: 3, cat: true } }, { name: "Alice", properties: { id: 2, cat: false } }],
eSet = new Set(existing.map(({ values_: { id, cat } }) => [id, cat].join('|'))),
result = newdata.filter(({ properties: { id, cat } }) => !eSet.has([id, cat].join('|')));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

JavaScript - Flatten Array of Nested Objects

I am working with an array of objects and need to flatten and re-assign the data set into an array of single objects using the object values as the new object keys.
Incoming Data:
results = [
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Jim' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Fredsburg' },
],
},
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Greg' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Waverly' },
],
},
];
Desired Result:
output = [
{
id: 1, FirstName: 'Jim', LastName: 'Jones', City: 'Fredsburg',
},
{
id: 2, FirstName: 'Greg', LastName: 'Jones', City: 'Waverly',
},
];
My current attempt gets me to placing the updated key/value pairs in the correct order, but creates a huge array of single objects.
Current Code:
const results = [];
surveyResults.map(s => s.surveyValues)
.forEach(s => Object.entries(s)
.forEach(([key, value]) => {
Object.entries(value)
.forEach(([k, v]) => {
if (k === 'name') {
results.push({
id: value.id,
[v]: value.value.toString(),
});
}
});
}));
Current Code Output:
[
{
id: 135, FirstName: 'Jim',
},
{
id: 136, LastName: 'Jones',
},
{
id: 137, City: 'Fredsburg',
},
{
id: 135, FirstName: 'Greg',
},
{
id: 136, LastName: 'Jones',
},
{
id: 137, City: 'Waverly',
},
]
What am I missing and why is my code not created the new arrays of objects as desired?
You may use map and reduce to accomplish this. I'm also accessing an index i in the map function's callback to set the id property and string.replace() to strip the space from the FirstName and LastName keys.
const results = [
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Jim' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Fredsburg' },
],
},
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Greg' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Waverly' },
],
},
];
const result = results.map((e, i) =>
e.surveyValues.reduce((a, e) => {
a[e.name.replace(" ", "")] = e.value;
return a;
}, {id: i + 1})
);
console.log(result);
Create each object in the outer map() loop, and add properties to it in the inner forEach().
surveyResults = [
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Jim' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Fredsburg' },
],
},
{
surveyValues: [
{ id: 135, name: 'First Name', value: 'Greg' },
{ id: 136, name: 'Last Name', value: 'Jones' },
{ id: 137, name: 'City', value: 'Waverly' },
],
},
];
const results = surveyResults.map((s, i) => {
let v = s.surveyValues;
let obj = {id: i};
v.forEach(item => obj[item.name.replace(/\s+/g, "")] = item.value.toString());
return obj;
});
console.log(results);

Javascript find node from tree with recursive function

I have JavaScript tree data like this.
const tree = {
children:[
{id: 10, children: [{id: 34, children:[]}, {id: 35, children:[]}, {id: 36, children:[]}]},
{id: 10,
children: [
{id: 34, children:[
{id: 345, children:[]}
]},
{id: 35, children:[]},
{id: 36, children:[]}
]
},
{id: 11, children: [{id: 30, children:[]}, {id: 33, children:[]}, {id: 3109, children:[]}]}
],
id: 45
}
const getByID = (tree, id) => {
let result = null
if (id === tree.id) {
return tree
} else {
if(tree.children){
tree.children.forEach( node=> {
result = getByID(node, id)
})
}
return result
}
}
const find345 = getByID(tree, 345)
console.log(find345)
I was try to find item by its id from this tree. im using recursive function to iterate the tree and its children, but it wont find the item as i expected.
its always return null. expected to return {id: 345, children:[]}
You need to exit the loop by using a method which allows a short circuit on find.
The problem with visiting nodes but already found the node, is the replacement of the result with a later wrong result. You need to exit early with a found node.
Array#some allows to iterate and to exit the loop if a truty value is returned. In this case the result is truthy on find.
const tree = { children: [{ id: 10, children: [{ id: 34, children: [] }, { id: 35, children: [] }, { id: 36, children: [] }] }, { id: 10, children: [{ id: 34, children: [{ id: 345, children: [] }] }, { id: 35, children: [] }, { id: 36, children: [] }] }, { id: 11, children: [{ id: 30, children: [] }, { id: 33, children: [] }, { id: 3109, children: [] }] }], id: 45 };
const getByID = (tree, id) => {
let result = null
if (id === tree.id) {
return tree
} else {
if(tree.children){
tree.children.some(node => result = getByID(node, id));
// ^^^^ exit if found
// ^^^^^^^^^^^^^^^^^^^^^^^^^^ return & assign
}
return result;
}
}
const find345 = getByID(tree, 345)
console.log(find345)
A bit shorter
var tree = { children: [{ id: 10, children: [{ id: 34, children: [] }, { id: 35, children: [] }, { id: 36, children: [] }] }, { id: 10, children: [{ id: 34, children: [{ id: 345, children: [] }] }, { id: 35, children: [] }, { id: 36, children: [] }] }, { id: 11, children: [{ id: 30, children: [] }, { id: 33, children: [] }, { id: 3109, children: [] }] }], id: 45 },
getByID = (tree, id) => {
var temp;
return tree.id === id
? tree
: (tree.children || []).some(o => temp = getByID(o, id)) && temp;
};
console.log(getByID(tree, 345));
We can also use reduce method for recursive function
function findAllByKey(obj, keyToFind) {
return Object.entries(obj)
.reduce((acc, [key, value]) => (key === keyToFind)
? acc.concat(value)
: (typeof value === 'object' && value)
? acc.concat(findAllByKey(value, keyToFind))
: acc
, []) || [];
}

Categories