I have a nested object with no pre-determinable path to child objects - example:
{
children :
[
{
children :
[
{
children :
[
{
id : "F100517B-D00F",
level : 4,
note : "update me",
parentId : "23A2A0DB-CCE3",
title : "change me"
}
],
id : "23A2A0DB-CCE3",
level : 3,
note : "me too",
parentId : "a0H4H00000Roi",
title : "..and me"
}
],
id : "a0H4H00000Roi",
level : 2,
note : "none",
parentId : "0064H00000ysL",
title : "pending"
},
{
"children" :
[
{
id : "6A45E2EC-7825",
level : 3,
note : "|",
parentId : "a0H4H00000Roi",
title : ""
}
],
id : "a0H4H00000Roi",
level : 2,
note : "",
parentId : "0064H00000ysL",
title : "Change me"
}
],
id : "0064H00000ysL",
level : 1,
note : "hello",
title : "Test Co"
}
Following this I generate a list of updates via a map function - sample results:
[{
content: "New Co",
id: "0064H00000ysL",
note: "Here's your update"
}, {
content: "91%",
id: "a0H4H00000Roi",
note: "New note here"
}]
I need to iterate through the update object array and update nested object values, I've tried a few things but can't seem to quite nail it (my JS skill is a bit limited atm).
Here's my last attempt, taken from the closest solution I found here:
Javascript update values in nested object by array path
var updates = $('div.node').map(function() {
return {
id: $(this).attr("id"),
content: $(this).find('div.content').text(),
note: $(this).find('div.note').text()
};
}).get();
const checkAndChange = (obj, update) => { //function to check id match for update
if (update.id.includes(obj.id)) {
obj.title = update.content;
obj.note = update.note;
}
}
const recursion = (obj, update) => {
const o = obj;
checkAndChange(o, update); // check if id exists update values
if (o.children.length > 0) { //check if has children
o.children.forEach(v => { //if has children do same recursion for children
recursion(v, update);
});
}
return o; //return updated object
}
var updatesLength = updates.length;
for(let it = 0; it < updatesLength; it++) {
recursion(obj, updates[it]);
}
console.log(obj)
The indented map function at the top works fine but I get Uncaught TypeError: Cannot read properties of undefined (reading 'id')" when I try to loop though the update array and write back to the main object (obj).
Any help appreciated
You can use a redursive approach here, we'll create a function updateNestedObj() to apply the updates, applying to each object and any of its child objects:
const objToUpdate = { children : [ { children : [ { children : [ { id : "F100517B-D00F", level : 4, note : "update me", parentId : "23A2A0DB-CCE3", title : "change me" } ], id : "23A2A0DB-CCE3", level : 3, note : "me too", parentId : "a0H4H00000Roi", title : "..and me" } ], id : "a0H4H00000Roi", level : 2, note : "none", parentId : "0064H00000ysL", title : "pending" }, { "children" : [ { id : "6A45E2EC-7825", level : 3, note : "|", parentId : "a0H4H00000Roi", title : "" } ], id : "a0H4H00000Roi", level : 2, note : "", parentId : "0064H00000ysL", title : "Change me" } ], id : "0064H00000ysL", level : 1, note : "hello", title : "Test Co" }
const updateArr = [{ content: "New Co", id: "0064H00000ysL", note: "Here's your update" }, { content: "91%", id: "a0H4H00000Roi", note: "New note here" }];
function updateNestedObj(obj, updates) {
const updateToApply = updates.find(upd => upd.id === obj.id);
if (updateToApply) {
obj.title = updateToApply.content;
obj.note = updateToApply.note;
}
// Apply updates to any child objects
for(let k in obj) {
if (typeof(obj[k]) === 'object') {
updateNestedObj(obj[k], updates);
}
}
}
updateNestedObj(objToUpdate, updateArr);
console.log(objToUpdate)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I am currently working in a project that has insisted in explicitly defining over 1,700 questions into a JSON data schema for an API and its getting out of control. I have been suggesting a more generic structure to the schema and let the data do the talking to tell you what the context is.
Whilst there are debates happening around which schema to use, we have decided on our internal systems, to go ahead and use a more generic model even if the externally facing model is the explicit one. This means we need an adapter to convert from one to the other, until such time as we can just use the one we wanted in the first place.
The business is a Java shop, I don't know whether to advise to build the adapter in Java or whether we can incorporate some lightweight JavaScript to do the work, maybe in the form of a configuration.
My question is: How would you approach converting the first JSON example into the second JSON example? It maps from explicitly defined objects to generic objects in arrays. Thanks for considering my question.
Example One
{
"nested_object" : {
"department_one" : {
"floor" : "4",
"product_one" : {
"quantity" : 10,
"size" : "L"
},
"product_two" : {
"quantity" : 5,
"size" : "S"
}
},
"department_two" : {
"floor" : "2",
"product_thirteen" : {
"quantity" : 1,
"size" : "M"
},
"product_eleven" : {
"quantity" : 8,
"size" : "L"
}
}
}
}
Example Two
{
"departments" : [
{
"department_name" : "department_one",
"floor" : "4",
"products" : [
{
"product_name" : "product_one",
"quantity" : 10,
"size" : "L"
},
{
"product_name" : "product_two",
"quantity" : 5,
"size" : "S"
}
]
},
{
"department_name" : "department_two",
"floor" : "2",
"products" : [
{
"product_name" : "product_thirteen",
"quantity" : 1,
"size" : "M"
},
{
"product_name" : "product_eleven",
"quantity" : 8,
"size" : "L"
}
]
}
]
}
You could use a combination of Object.keys (to grab product and department names). Below is a quick implementation.
const obj1 = {
"nested_object" : {
"department_one" : {
"floor" : "4",
"product_one" : {
"quantity" : 10,
"size" : "L"
},
"product_two" : {
"quantity" : 5,
"size" : "S"
}
},
"department_two" : {
"floor" : "2",
"product_thirteen" : {
"quantity" : 1,
"size" : "M"
},
"product_eleven" : {
"quantity" : 8,
"size" : "L"
}
}
}
}
const transformedObj = {
departments: [ ],
};
//holds all department names
const departmentKeys = Object.keys(obj1.nested_object)
const departmentsArr = departmentKeys.map((key) => {
const floor = obj1.nested_object[key].floor
//remove floor reference, since we already stored the value above
delete obj1.nested_object[key].floor
//holds all product names
const productsKeysArr = Object.keys(obj1.nested_object[key])
//holds all product objects for respective department
const productsArr = productsKeysArr.map((product) => {
const quantity = obj1.nested_object[key][product].quantity
const size = obj1.nested_object[key][product].size
return {
product_name: product,
quantity: quantity,
size: size
}
})
return {
department_name: key,
floor: floor,
products: productsArr
}
})
//assign departments array to transformed object
transformedObj.departments = departmentsArr
console.log(transformedObj)
This would be my take on this. I like conciseness and expressiveness in implementations:
const data = { "nested_object": { ... }}
Object.entries(data.nested_object).map(([department_name, {floor, ...ps}]) => ({
department_name,
floor,
products: Object.entries(ps).map(([product_name, p]) => ({product_name, ...p}))
}))
In my angularjs application, I have the following list of objects.
$scope.itemsList = [
{
"setupId": "T_2893",
"name" : "abc"
},
{
"setupId": "LBT826",
"name" : "xyz"
},
{
"setupId": "LBT1252",
"name" : "pqr"
},
{
"setupId": "G1252",
"name" : "dwr"
}
]
Now when I call $scope.changeOreder(1, 3) function it should reorder the objects based on the prev and next index. so the list should be as follows.
$scope.itemsList = [
{
"setupId": "T_2893",
"name" : "abc"
},
{
"setupId": "LBT1252",
"name" : "pqr"
},
{
"setupId": "G1252",
"name" : "dwr"
},
{
"setupId": "LBT826",
"name" : "xyz"
}
]
Now if I call, $scope.changeOreder(2, 0), the new list should be,
$scope.itemsList = [
{
"setupId": "G1252",
"name" : "dwr"
},
{
"setupId": "T_2893",
"name" : "abc"
},
{
"setupId": "LBT1252",
"name" : "pqr"
},
{
"setupId": "LBT826",
"name" : "xyz"
}
]
In my $scope.changeOrder function, I have tried different ways, like taking the back up of the object at prevIndex , then deleting the obj at prevIndex to insert the backed up obj at newIndex, but because I have deleted the object the newIndex is no more valid in the current list!!!. Like this I tried different different ways but the final list is not getting ordered the way I am expecting. Can any one help me in fixing it.
Moving an item to a specified index:
var items = [
{
"setupId": "G1252",
"name" : "dwr"
},
{
"setupId": "T_2893",
"name" : "abc"
},
{
"setupId": "LBT1252",
"name" : "pqr"
},
{
"setupId": "LBT826",
"name" : "xyz"
}
];
function moveItem(posA, posB) {
/*
* If an item is moved from a higher index to a lower index, then we need to
* remove the current item first, then add it.
* When moving a item from a low index to a higer index, then we need to add
* the item first, then delete it.
*/
var upToDown = posA > posB;
var itemToMove = items[posA];
//Make copy first
var tmpList = items.slice(0);
if (!upToDown) {
//Add item to specified index
tmpList.splice(posB+1, 0, itemToMove);
//Remove the old item
tmpList.splice(posA, 1);
} else {
//Remove the old item
tmpList.splice(posA, 1);
//Add item to specified index
tmpList.splice(posB, 0, itemToMove);
}
return tmpList;
}
var result = moveItem(0, 3);
console.log(result);
Plunk: https://plnkr.co/edit/23DGzcXBEY7qrqFWsQNA?p=preview
I have two data structures with different shapes that come from two different API's. The data is in JSON format, language is JavaScript.
Array 1:
[ { "document" : { "html" : "some_html", "name" : "DOCUMENT_NAME_1" },
"tooltips" : [ { "html" : "some_html", "name" : "TASK_NAME_1" },
{ "html" : "some_html", "name" : "TASK_NAME_2" } ] },
{ "document" : { "html" : "some_html", "name" : "DOCUMENT_NAME_2" },
"tooltips" : [ { "html" : "some_html", "name" : "TASK_NAME_3" },
{ "html" : "some_html", "name" : "TASK_NAME_4" } ] }]
Array 2:
[ [ { "name" : "TASK_NAME_1", "status" : "FINISHED" },
{ "name" : "TASK_NAME_2", "status" : "OPEN" } ],
[ { "name" : "TASK_NAME_3", "status" : "OPEN" },
{ "name" : "TASK_NAME_4", "status" : "FUTURE" } ] ]
The elements of the tooltips field from the array 1 contain the same "name"s as elements of array 2. How can I elegantly merge "status" from the array 2 into tooltips within array 1?
I thought that lenses could be the right answer, but I'm unsure as I've never used them.
I am aware of some ways I could solve it using nested iteration and updating array 1. I am ideally looking for an approach that doesn't modify existing data structures.
This is little complex but it should work for you
array2.forEach(function(tooltips){
tooltips.forEach(function(tooltip){
for (var i = 0; i < array1.length; i++) {
for (var j = 0; j < array1[i].tooltips.length; j++) {
var arr1Tooltip = array1[i].tooltips[j];
if(arr1Tooltip.name == tooltip.name)
arr1Tooltip.status = tooltip.status;
};
};
});
});
console.log(JSON.stringify(array1));
This is probably way over engineered and not very efficient, but you can do it as with this JSFiddle using recursive functions. I'm too tired to do it in a clever way.
var arr1 = [ { "document" : { "html" : "some_html", "name" : "DOCUMENT_NAME_1" },
"tooltips" : [ { "html" : "some_html", "name" : "TASK_NAME_1" },
{ "html" : "some_html", "name" : "TASK_NAME_2" } ] },
{ "document" : { "html" : "some_html", "name" : "DOCUMENT_NAME_2" },
"tooltips" : [ { "html" : "some_html", "name" : "TASK_NAME_3" },
{ "html" : "some_html", "name" : "TASK_NAME_4" } ] }];
var arr2 = [ [ { "name" : "TASK_NAME_1", "status" : "FINISHED" },
{ "name" : "TASK_NAME_2", "status" : "OPEN" } ],
[ { "name" : "TASK_NAME_3", "status" : "OPEN" },
{ "name" : "TASK_NAME_4", "status" : "FUTURE" } ] ];
var findStatus = function(name, searchArray) {
var r = '';
if (typeof searchArray === 'object') {
if ("name" in searchArray && "status" in searchArray) {
if (searchArray.name == name) {
return searchArray.status;
} else {
return '';
}
} else {
for (var i in searchArray) {
r = findStatus(name, searchArray[i]);
if (r != '') {
return r;
}
}
}
}
return '';
};
var updateStatus = function(arrToUpdate, arrWithStatus) {
var copy = $.extend(true, {}, arrToUpdate);
var r = '';
if (typeof copy === 'object') {
if ("name" in copy) {
r = findStatus(copy.name, arrWithStatus);
if (r != '') {
copy.status = r;
}
} else {
for (var i in copy) {
copy[i] = updateStatus(copy[i], arrWithStatus);
}
}
}
return copy;
};
var arr3 = updateStatus(arr1, arr2); // Final combined array
I added the var copy = $.extend(true, {}, arrToUpdate); line so that it will do a deep copy and not modify the original array, as a result, it requires jQuery.
Since your data structure is nested, you will need two zip.map/zipWiths:
zip(array1, array2).map(function([obj, tooltips]) { // ES6 destructuring syntax
return {
document: obj.document,
tooltips: zip(obj.tooltips, tooltips).map(function([tooltip, extender]) {
return {
html: tooltip.html,
name: tooltip.name,
status: extender.status
};
})
};
})
If you don't like to repeat those object literal structures, you might be able to use some copying function; for example
extend({}, document, {tooltips:…})
extend({}, tooltip, extender);
You also might use a lenses library like https://github.com/DrBoolean/lenses or https://github.com/fantasyland/fantasy-lenses for that, but I'm not sure whether that's worth the effort - the above code only needs underscore/lodash.
To get around the inner zipWith, you would need a Traversal lens (I assume you're familiar with this article), but I haven't yet seen a JavaScript library that offers such.
I have one JSON Object like this :
var myObject = [
{
"Name" : "app1",
"id" : "1",
"groups" : [
{ "id" : "test1",
"name" : "test group 1",
"desc" : "this is a test group"
},
{ "id" : "test2",
"name" : "test group 2",
"desc" : "this is another test group"
}
]
},
{
"Name" : "app2",
"id" : "2",
"groups" : [
{ "id" : "test3",
"name" : "test group 4",
"desc" : "this is a test group"
},
{ "id" : "test4",
"name" : "test group 4",
"desc" : "this is another test group"
}
]
},
{
"Name" : "app3",
"id" : "3",
"groups" : [
{ "id" : "test5",
"name" : "test group 5",
"desc" : "this is a test group"
},
{ "id" : "test6",
"name" : "test group 6",
"desc" : "this is another test group"
}
]
}
];
I have new value available of "name" for specific "id".
How can I replace "name" of specific "id" inside any object ?
And how to count total number of groups among all objects ?
for example : replace name to "test grp45" for id = "test1"
Here is fiddle
http://jsfiddle.net/qLTB7/21/
The following function will search through an object and all of its child objects/arrays, and replace the key with the new value. It will apply globally, so it won't stop after the first replacement. Uncomment the commented line to make it that way.
function findAndReplace(object, value, replacevalue) {
for (var x in object) {
if (object.hasOwnProperty(x)) {
if (typeof object[x] == 'object') {
findAndReplace(object[x], value, replacevalue);
}
if (object[x] == value) {
object["name"] = replacevalue;
// break; // uncomment to stop after first replacement
}
}
}
}
Working jsfiddle: http://jsfiddle.net/qLTB7/28/
Try this
function findAndReplace(object,keyvalue, name) {
object.map(function (a) {
if (a.groups[0].id == keyvalue) {
a.groups[0].name = name
}
})
}
findAndReplace(myObject,"test1" ,"test grp45");
Here's a different approach using Array.prototype.some. It assumes that the Name property in the outer objects should be actually be name (note capitalisation).
function updateNameById(obj, id, value) {
Object.keys(obj).some(function(key) {
if (obj[key].id == id) {
obj[key].name = value;
return true; // Stops looping
}
// Recurse over lower objects
else if (obj[key].groups) {
return updateNameById(obj[key].groups, id, value);
}
})
}
The advantage of some is that it stops as soon as the callback returns true.
I think this should work for you:-
var id = 'test1';
var newname = 'test grp45';
var numberOfGruops = 0;
myObject.forEach(function(app){
numberOfGruops += app.groups.length; //Count all groups in this app
app.groups.forEach(function(group){
if(group.id===id)
group.name = newname; // replace the name
});
});
Maybe a more succinct sol'n
function changeName(objArray, objId, newName) {
objArray.forEach(function(obj) {
if (obj.id === objId) obj.Name = newName;
});
}
Personally: if this were me, when creating these objects, I would create a new obj and key them by id.
var myApps = {};
myObject.forEach(function(o) {
myApps[o.id] = o;
});
=>
{
"1": {
"Name": "app1",
"id": "1",
"groups": [
{
"id": "test1",
"name": "test group 1",
"desc": "this is a test group"
},
{
"id": "test2",
"name": "test group 2",
"desc": "this is another test group"
}
]
}
}
And then you could just do:
myApps['someId'].name = 'This is my new Name'
Check it out here:
http://jsfiddle.net/qLTB7/40/
it should be if (object["id"] == value) instead of if (object[x] == value) in 7th line of PitaJ answer, so whole function will look like:
function findAndReplace(object, value, replacevalue) {
for (var x in object) {
if (object.hasOwnProperty(x)) {
if (typeof object[x] == 'object') {
findAndReplace(object[x], value, replacevalue);
}
if (object["id"] == value) {
object["name"] = replacevalue;
// break; // uncomment to stop after first replacement
}
}
}
}
if you leave object[x] - function will replace name also for objects with other keys values set to "test1", for example
{"id": "xxx", "name": "test group 1", "desc": "test1"}
I have this data in Mongo:
{'_id':1,
'name':'Root',
'taskId':1,
'parentId':"",
'path':[1],
'tasks':[ {"taskId":3,parentId:1,name:'A',type:'task'},
{"taskId":4,parentId:1,name:'D',type:'task'},
{"taskId":5,parentId:4,name:'B',type:'task'},
{'type':'project' , 'proRef':2},
{"taskId":6,parentId:3,name:'E',type:'task'},
{"taskId":7,parentId:6,name:'C',type:'task'}]
}
Now I want to update taskId 6 with new Json data .
var jsonData = {"taskId":6,"name":'Sumeet','newField1':'Val1','newField2':'Val2'}
query should update if field is available else add new key to existing .Output Like
{"taskId":6,parentId:3,name:'Sumeet',type:'task','newField1':'Val1','newField2':'Val2'}]
I have tried few query but it is completely replacing json .
db.projectPlan.update({_id:1,'tasks.taskId':6},{$set :{'tasks.$':jsonData }});
Thanks in advance for your helps!
Sumeet
You need to transform the jsonData variable into something that can be passed to update. Here's an example that does exactly what you want with your sample document:
var updateData = {};
for (f in jsonData) {
if (f != "taskId") updateData["tasks.$."+f]=jsonData[f];
};
db.projectPlan.update({_id:1, 'tasks.taskId':6}, {$set:updateData})
Result:
{ "_id" : 1,
"name" : "Root",
"taskId" : 1,
"parentId" : "",
"path" : [ 1 ],
"tasks" : [
{ "taskId" : 3, "parentId" : 1, "name" : "A", "type" : "task" },
{ "taskId" : 4, "parentId" : 1, "name" : "D", "type" : "task" },
{ "taskId" : 5, "parentId" : 4, "name" : "B", "type" : "task" },
{ "type" : "project", "proRef" : 2 },
{ "taskId" : 6, "parentId" : 3, "name" : "Sumeet", "type" : "task", "newField1" : "Val1", "newField2" : "Val2" },
{ "taskId" : 7, "parentId" : 6, "name" : "C", "type" : "task" }
] }
You will need to merge the document manually:
var jsonData = {"taskId":5,"name":'Sumeet','newField1':'Val1','newField2':'Val2'};
db.projectPlan.find({ _id: 1 }).forEach(
function(entry) {
for (var taskKey in entry.tasks) {
if (entry.tasks[taskKey].taskId === jsonData.taskId) {
printjson(entry.tasks[taskKey]);
for (var taskSubKey in jsonData) {
entry.tasks[taskKey][taskSubKey] = jsonData[taskSubKey];
}
printjson(entry.tasks[taskKey]);
}
}
db.projectPlan.save(entry);
}
);
Obviously you can leave away the printjson statements. This is simply to see that the merging of the original tasks with the new tasks works. Note that this query will only update a single document as long as the _id field is unique.