I have a JSON tree likes this Github link
As you can see, this tree uses formula property on each item to calculate its value. So I need to calculate the value of a few nodes or root nodes.
You can see PL6A, PL6B is the end of tree with type = 1. So I need to replace up to the tree so which formula construct by PLVALUE must be replaced with ACVALUE.
Example:
PL10 formula must be replace with (ACVALUE(PL6A)+ACVALUE(PL6B))+...
How can I do that, thank you and sorry for bad English
Update
I have tried this code:
for (let i = 0; i < accounts.length; i++) {
const item = accounts[i];
let { formula } = accounts[i];
if (item.children.length && item.type === 2) {
//Replace formula with full children formula
item.children.forEach((child) => {
if (formula.indexOf(child.code) > -1) {
const fullInfoChild = accounts.find(
(fullInfoItem) => _.trim(fullInfoItem._id) === _.trim(child._id)
);
formula = _.replace(
formula,
`PLVALUE(${child.code})`,
`(${fullInfoChild.formula})`
);
}
});
} else {
formula = item.formula;
}
accounts[i].formula = formula;
}
and the result, some node work well
{
"_id": "5cf6159f386f5942aabca347",
"code": "PL10",
"formula": "(ACVALUE(PL6A)+ACVALUE(PL6B))+(ACVALUE(PL9))+(ACVALUE(PL7B)+ACVALUE(PL7A))+(ACVALUE(PL8A)+ACVALUE(PL8B))+(ACVALUE(DSRGP))",
"status": 1,
"type": 2,
"parents": [
{
"_id": "5cf61756386f5942aabca365",
"code": "PL28",
"formula": "PLVALUE(PL10)+PLVALUE(PL15)",
"status": 1,
"type": 2
}
],
"children": [
{
"_id": "5cf614f4386f5942aabca342",
"code": "PL6",
"formula": "ACVALUE(PL6A)+ACVALUE(PL6B)",
"status": 1,
"type": 2
},
{
"_id": "5cf6156a386f5942aabca346",
"code": "PL_DSRGP",
"formula": "ACVALUE(DSRGP)",
"status": 1,
"type": 2
},
{
"_id": "5cf6152f386f5942aabca345",
"code": "PL9",
"formula": "ACVALUE(PL9)",
"status": 1,
"type": 2
},
{
"_id": "5cf6150e386f5942aabca343",
"code": "PL7",
"formula": "ACVALUE(PL7B)+ACVALUE(PL7A)",
"status": 1,
"type": 2
},
{
"_id": "5cf6151e386f5942aabca344",
"code": "PL8",
"formula": "ACVALUE(PL8A)+ACVALUE(PL8B)",
"status": 1,
"type": 2
}
],
"totalCurrentYear": 0
},
but if we up some level it not work like this one
{
"_id": "5cf8c78b4aafe73cb56ce424",
"code": "PL30",
"formula": "(((ACVALUE(PL6A)+ACVALUE(PL6B))+(ACVALUE(PL9))+(ACVALUE(PL7B)+ACVALUE(PL7A))+(ACVALUE(PL8A)+ACVALUE(PL8B))+(ACVALUE(DSRGP)))+((ACVALUE(511000)+ACVALUE(735013)+ACVALUE(511100))+(ACVALUE(511002)+ACVALUE(511101)+ACVALUE(554020)+ACVALUE(735015))+(ACVALUE(511003)+ACVALUE(511102)+ACVALUE(735016))+(ACVALUE(511004)+ACVALUE(554010)+ACVALUE(554021)+ACVALUE(735017)+ACVALUE(554030))))+(PLVALUE(PL23)+PLVALUE(PL24)+PLVALUE(PL25))",
"status": 1,
"type": 2,
"children": [
{
"_id": "5cf61756386f5942aabca365",
"code": "PL28",
"formula": "PLVALUE(PL10)+PLVALUE(PL15)",
"status": 1,
"type": 2
},
{
"_id": "5cf8c7bd4aafe73cb56ce426",
"code": "PL26",
"formula": "PLVALUE(PL23)+PLVALUE(PL24)+PLVALUE(PL25)",
"status": 1,
"type": 2
}
],
},
Updated: Add full json data to github link
You could collect the references to the code/formula and the formulas to replace and iterate the formulas to get the values.
var data = [{ _id: "5cf8c78b4aafe73cb56ce424", code: "PL30", formula: "PLVALUE(PL28)+PLVALUE(PL26)", status: 1, type: 2, children: [{ _id: "5cf61756386f5942aabca365", code: "PL28", formula: "PLVALUE(PL10)+PLVALUE(PL15)", status: 1, type: 2 }, { _id: "5cf8c7bd4aafe73cb56ce426", code: "PL26", formula: "PLVALUE(PL23)+PLVALUE(PL24)+PLVALUE(PL25)", status: 1, type: 2 }] }, { _id: "5cf61756386f5942aabca365", code: "PL28", formula: "PLVALUE(PL10)+PLVALUE(PL15)", status: 1, type: 2, children: [{ _id: "5cf6159f386f5942aabca347", code: "PL10", formula: "PLVALUE(PL6)+PLVALUE(PL9)+PLVALUE(PL7)+PLVALUE(PL8)+PLVALUE(PL_DSRGP)", status: 1, type: 2 }, { _id: "5cf61741386f5942aabca364", code: "PL15", formula: "PLVALUE(PL11)+PLVALUE(PL12)+PLVALUE(PL13)+PLVALUE(PL14)", status: 1, type: 2 }], totalCurrentYear: 0 }, { _id: "5cf6159f386f5942aabca347", code: "PL10", formula: "PLVALUE(PL6)+PLVALUE(PL9)+PLVALUE(PL7)+PLVALUE(PL8)+PLVALUE(PL_DSRGP)", status: 1, type: 2, children: [{ _id: "5cf614f4386f5942aabca342", code: "PL6", formula: "ACVALUE(PL6A)+ACVALUE(PL6B)", status: 1, type: 2 }, { _id: "5cf6156a386f5942aabca346", code: "PL_DSRGP", formula: "ACVALUE(DSRGP)", status: 1, type: 2 }, { _id: "5cf6152f386f5942aabca345", code: "PL9", formula: "ACVALUE(PL9)", status: 1, type: 2 }, { _id: "5cf6150e386f5942aabca343", code: "PL7", formula: "ACVALUE(PL7B)+ACVALUE(PL7A)", status: 1, type: 2 }, { _id: "5cf6151e386f5942aabca344", code: "PL8", formula: "ACVALUE(PL8A)+ACVALUE(PL8B)", status: 1, type: 2 }] }, { _id: "5cf614f4386f5942aabca342", code: "PL6", formula: "ACVALUE(PL6A)+ACVALUE(PL6B)", status: 1, type: 2, children: [{ _id: "5cf61869386f5942aabca368", code: "PL6A", type: 1, status: 1 }, { _id: "5cf6187f386f5942aabca36a", code: "PL6B", type: 1, status: 1 }], totalCurrentYear: 0 }],
replace = (formula, values) => formula.replace(/PLVALUE\(([^\)]+)\)/g, (full, group) => group in values
? `(${values[group].object[values[group].key]})`
: full
),
getNestedValues = (r, object) => {
r.values[object.code] = { object, key: object.type === 1 ? 'code' : 'formula' };
if (object.formula?.includes('PLVALUE')) r.formulas.push(object);
if (object.children) object.children.reduce(getNestedValues, r);
return r;
},
{ values, formulas } = data.reduce(getNestedValues, { values: {}, formulas: [] });
// while (formulas.length) { // only if all targets are known
let i = formulas.length;
while (i--) {
let s = replace(formulas[i].formula, values);
if (s === formulas[i].formula) continue;
formulas[i].formula = s;
formulas.splice(i, 1);
}
// } // only if all targets are known
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
The issue I am facing is that even though I am able to load the data in the database but the desired result is different from what is being returned.
The issue is that whenever I am running for loop the qn variable is inserted twice but need it only once.
Please if someone can help me resolve this issue.
userProject.js
router.post('/notification/check/upload/survey' , async (req,res) => {
//const taginsert = [];
const mcq = [];
//const newData = {};
//const o = {};
console.log(req.body);
for(var i=0; i<=req.body.questions.length-1;i++) {
// newData = {
// qn: req.body.questions[i].qn
// }
for(var j=0; j<=req.body.questions[i].options.length-1;j++){
const o =
{
qn: req.body.questions[i].qn,
//{
options: [{
option: req.body.questions[i].options[j].option,
totalValues: req.body.questions[i].options[j].totalValues
}]
}
// newData.options = o;
// newData.push(o);
mcq.push(o);
}
}
//mcq = newData;
const check = new userCampaignSurvey({
userId: req.body.userId,
status: 'Pending',
notification_type:req.body.notification_type,
questions: mcq
})
check.save((err,p) => {
if(err) {
res.status(500).send(err);
}
res.status(201).send(p);
})
})
model.js
userId:
{
type: Number,
required: true,
trim: true
},
notification_id:
{
type: Number,
},
notification_type:
{
type: Number,
required: true,
},
status:
{
type: String,
required: true
},
questions: [
{
qn: {
type: String,
required: true
},
options: [
{
option: {
type: String
},
totalViews: {
type:Number
}
}
]
},
],
created_at: {
type: Date,
required: true,
default: Date.now(),
},
});
result received
{
"userId": 1,
"notification_type": 1,
"status": "Pending",
"questions": [
{
"qn": "Which brand do you like better for buying shoes?",
"options": [
{
"option": "Adidas",
"_id": "639064c9ec215f2cd1b2b15d"
}
],
"_id": "639064c9ec215f2cd1b2b15c"
},
{
"qn": "Which brand do you like better for buying shoes?",
"options": [
{
"option": "Reebok",
"_id": "639064c9ec215f2cd1b2b15f"
}
],
"_id": "639064c9ec215f2cd1b2b15e"
}
],
"created_at": "2022-12-07T10:02:48.442Z",
"_id": "639064c9ec215f2cd1b2b15b",
"__v": 0
}
expected result
{
"userId": 1,
"status": "Pending",
"notification_type": 1,
"questions": [
{
"qn": "Which brand do you like better for buying shoes?",
"options": [
{
"option": "Adidas",
"totalViews": 20
},
{
"option": "Reebok",
"totalViews": 10
}
]
}
]
}
The following data structure is an array and each array element being an object with keys:
this.objectImWorkingWith = [
{
"1306": {
"id": 6460,
"data": "Process",
},
"1307": {
"id": 6461,
"data": "1287",
},
"1309”: {
"id": 6462,
"data": “2748”,
}
},
{
"1306": {
"id": 6465,
"data": "Product",
},
"1307": {
"id": 6466,
"data": "2574",
},
"1309”: {
"id": 6263,
"data": “3752”,
}
},
{
"1306": {
"id": 6470,
"data": "Research",
},
"1307": {
"id": 6471,
"data": "3861",
},
"1307": {
"id": 6472,
"data": “2248”,
}
}
]
Each element within the array has multiple objects within it and I want to retrieve the value of 'data' for each object within the parent object whilst retaining the structure. Meaning that I want to keep, for example, Process, 1287 and 2748 grouped together and so on.
So in summary I am aiming to get the value of "data" for each object within the array but keep all values of data that are in the same object in the array associated to each other.
I've tried
Object(this.allRelevantData.map(a=>a.data))
however it yields
[undefined, undefined, undefined]
You could try this:
var objectImWorkingWith = [
{
1306: {
id: 6460,
data: "Process",
},
1307: {
id: 6461,
data: "1287",
},
1309: {
id: 6462,
data: "2748",
},
},
{
1306: {
id: 6465,
data: "Product",
},
1307: {
id: 6466,
data: "2574",
},
1309: {
id: 6263,
data: "3752",
},
},
{
1306: {
id: 6470,
data: "Research",
},
1307: {
id: 6471,
data: "3861",
},
1307: {
id: 6472,
data: "2248",
},
},
];
const result = objectImWorkingWith.map((object) => {
const data = [];
for (var key of Object.keys(object)) {
data.push(object[key].data);
}
return data;
});
variable result will contain:
[["Process", "1287", "2748"],
["Product", "2574", "3752"],...]
I already know that editing object within an array doesn't work for vue.js due to its limited capability and base on what I read using vue.set should solve this problem easily but I am having a hard time making it work.
this is my sample data. The parent object is called resource and inside resource there are power_generator_groups , inside the power_generator_groups are power_generators.
{
"id": 5,
"bg_member_id": 1,
"code": "G0633",
"name": "namex1",
"power_generator_groups": [
{
"id": 1,
"resource_id": 5,
"code": "GC033",
"power_generators": [
{
"id": 1,
"power_generator_group_id": 1,
"code": "XXXXX",
"name": "test",
"contract_number": "00000003",
"supply_max": 1000,
},
{
"id": 2,
"power_generator_group_id": 1,
"code": "XXXXX",
"name": "test",
"contract_number": "00000003",
"supply_max": 1000,
},
{
"id": 3,
"power_generator_group_id": 1,
"code": "XXXXX",
"name": "test",
"contract_number": "00000003",
"supply_max": 1000,
}
]
},
{
"id": 2,
"resource_id": 5,
"code": "GC033",
"contract_number": "065C001",
"power_generators": [
{
"id": 4,
"power_generator_group_id": 2,
"code": "XXXXX",
"name": "test",
"contract_number": "00000003",
"supply_max": 1000,
}
]
}
]
}
and this is my vue file
<template lang="pug">
.wrapper
.animated.fadeIn
b-container(fluid='')
b-row.my-1
b-col(sm='3')
label(:for='`type-key`') companyName
b-col(sm='9')
b-form-select(v-model='item.bg_member_id' :options="companyList")
b-col(sm='3')
label(:for='`type-key`') code
b-col(sm='9')
b-form-input(v-model='item.code')
b-button(size='sm', #click='addPowerGeneratorGroup', variant="primary")
| Add power_generator_group
template(v-for='(group, group_index) in item.power_generator_groups')
b-card(
header-tag="header"
footer-tag="footer"
)
div(slot="header")
i.fa.fa-align-justify
strong power_generator_groups
button.btn.btn-default.btn-sm(type='button' #click='deletePowerGeneratorGroup(group_index)')
span.fa.fa-trash-o
div
b-row.my-1
b-col(sm='3')
label(:for='`type-key`')
| code
b-col(sm='9')
b-form-input(v-model='group.code')
b-col(sm='3')
label(:for='`type-key`') group name
b-col(sm='9')
b-form-input(v-model='group.name')
b-col(sm='3')
label(:for='`type-key`') contract number
b-col(sm='9')
b-form-input(v-model='group.contract_number')
b-container.bv-example-row(fluid='')
b-button(size='sm', #click='addPowerGenerator(group_index)', variant="primary")
| add power_generator
b-card(
header-tag="header"
footer-tag="footer"
)
div(slot="header")
i.fa.fa-align-justify
strong power_generators
b-table(small v-bind:item="group.power_generators" v-bind:fields="fields" fixed responsive)
template(v-for="field in fields" :slot="field.key" slot-scope="contract")
template(v-if="field.key == 'actions'")
b-button(size='sm', #click='deletePowerGenerator(group_index, contract.index)', variant="primary")
| Del
template(v-else)
b-form-input(:type='types[field.key]' v-model = 'contract.item[field.key]')
b-button(size='sm', #click='save', variant="primary")
| Save
</template>
<script>
import Vue from 'vue';
export default {
props: {
item: {
type: Object,
required: true,
default: () => {
}
},
companyList: Array
},
data() {
return {
companyId: null,
code: null,
name: null,
contract_number: null,
fields: [
{key: 'actions', label: '' },
{key: 'code', label: 'code', sortable: true, sortDirection: 'desc'},
{key: 'name', label: 'name', sortable: true, class: 'text-center'},
{key: 'contract_number', label: 'contractNo'},
{key: 'supply_max', label: 'maxunit'}
],
types: {
code: 'text',
name: 'text',
contract_number: 'text',
supply_max: 'number'
},
items:
{}
}
},
mounted() {
},
computed: {},
methods: {
addPowerGenerator(index) {
console.log(index)
console.log(this.item)
const pgg = this.item.power_generator_groups[index];
const pgs = pgg.power_generators || Vue.set(pgg, 'power_generators', []);
pgs.push(
{
"code": "",
"name": "",
"contract_number": "",
"supply_max": "",
}
)
},
addPowerGeneratorGroup() {
const pggs = this.item.power_generator_groups || Vue.set(this.item, 'power_generator_groups', []);
pggs.push(
{
"resource_id": this.item.id,
"name": "",
"code": "",
"contract_number": "",
"power_generators": []
}
)
},
deletePowerGenerator(group_index, field_index) {
this.item.power_generator_groups[group_index].power_generators.splice(field_index, 1);
},
deletePowerGeneratorGroup(group_index) {
this.item.power_generator_groups.splice(group_index, 1);
},
save: function () {
this.$parent.save(this.item)
}
}
}
</script>
You'll need Vue.set only if you have power_generator_groups that have no power_generators property, since Vue can't detect when you add it otherwise.
Assuming there are no problems with your template (you didn't show it), change your addPowerGenerator method:
addPowerGenerator(index) {
console.log(index)
console.log(this.item)
const pgg = this.item.power_generator_groups[index];
const pgs = pgg.power_generators || Vue.set(pgg, 'power_generators', []);
pgs.push(
{
"code": "",
"name": "",
"contract_number": "",
"supply_max": "",
}
)
}
Vue.set returns the property you just created which is how pgs contains the proper reference in that case.
Do the same thing for the new method you edited in:
addPowerGeneratorGroup() {
const pggs = this.item.power_generator_groups || Vue.set(this.item, 'power_generator_groups', []);
pggs.push(
{
"resource_id": this.item.id,
"name": "",
"code": "",
"contract_number": "",
"power_generators": []
}
)
}
You can see a mockup here where I've assumed this.item refers to the resource itself. I included a 3rd group which has no power_generators property for testing.
Imagine the following model:
var Office =
{
id: 1,
name: "My Office",
branches:
[
{
adddress: "Some street, that avenue",
isPrincipal: true,
},
{
adddress: "Another address",
isPrincipal: false,
},
]
}
I'd like to remove a branch, but we can't let the user remove the principal branch from an office. So here's my function:
remove: function(body)
{
return new Promise(function(resolve, reject)
{
return Office.findByIdAndUpdate(1, { $pull: {'branches': {_id: body.branch.id}}}, { new: true })
.then(function(updatedOffice){
resolve(updatedOffice)
})
.catch(function(error){
reject(error);
});
})
}
I have some doubts here:
As you can see I haven't included the another WHERE on the isPrincipal property, this is because I don't know how can I determine whether the office object got actually changed. Because the object will alway be retrieved but... How can I know this for sure?
Is FindByIdAndUpdate the best approach considering we can't let a user delete the principal branch, and if he's trying to do so, we have to show a warning.
The only real reliable way to see if an update was applied for something like a $pull is to basically check the returned document and see if the data you intended to $pull is still in there or not.
That's for any of the "findAndUpdate" variety of actions, and there is a valid reason for that as well as it also being the case that a plain .update() will actually "reliably" tell you if the modification was in fact made.
To walk through the cases:
Check the Returned Content
This basically involves looking at the array in the returned document in order to see if what we asked to remove is actually there:
var pullId = "5961de06ea264532c684611a";
Office.findByIdAndUpdate(1,
{ "$pull": { "branches": { "_id": pullId } } },
{ "new": true }
).then(office => {
// Check if the supplied value is still in the array
console.log(
"Still there?: %s",
(office.branches.find( b => b._id.toHexString() === pullId))
? true : false
);
}).catch(err => console.error(err))
We use .toHexString() in order to compare the actual value from an ObjectId since JavaScript just does not do "equality" with "Objects". You would check on both "left" and "right" if supplying something that was already "cast" to an ObjectId value, but in this case we know the other input is a "string".
Just use .update(), "It's reliable"
The other case here to consider brings into question if you "really need" the returned modified data anyway. Because the .update() method, will reliably return a result telling you if anything was actually modified:
Office.update(
{ "_id": 1 },
{ "$pull": { "branches": { "_id": pullId } } },
).then(result => {
log(result);
}).catch(err => console.error(err))
Where result here will look like:
{
"n": 1,
"nModified": 1, // <--- This always tells the truth, and cannot lie!
"opTime": {
"ts": "6440673063762657282",
"t": 4
},
"electionId": "7fffffff0000000000000004",
"ok": 1
}
And in which the nModified is a "true" indicator of whether something "actually updated". Therefore if it's 1 then the $pull actually had an effect, but when 0 nothing was actually removed from the array and nothing was modified.
This is because the method actually uses the updated API, which does have reliable results indicating actual modifications. The same would apply to something like a $set which did not actually change the value because the the value supplied was equal to what already existed in the document.
findAndModify Lies!
The other case here you might think of when looking closely at the documentation is to actually inspect the "raw result" and see if the document was modified or not. There is actually an indicator in the specification for this.
The problem is ( as well as requiring more work with Promises ) that the result is not actually truthful:
var bogusId = "5961de06ea264532c684611a"; // We know this is not there!
Promise((resolve,reject) => {
Office.findByIdAndUpdate(1,
{ "$pull": { "branches": { "_id": bogusId } } },
{ "new": true, "passRawResult" },
(err,result,raw) => { // We cannot pass multiple results to a Promise
if (err) reject(err);
resolve({ result, raw }); // So we wrap it!
}
)
})
.then(response => log(response.raw))
.catch(err => console.error(err));
The problem here is that even when we "know" this should not modify, the response says otherwise:
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1 // <--- LIES! IT'S ALL LIES!!!
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961de06ea264532c6846118"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
So even after all that work to get the "third" argument out of the callback response, we still did not get told the correct information about the update.
Concluding
So if you want to "reliably" do this with a single request ( and you cannot reliably do that with multiple requests since the document could change in between! ) then your two options are:
Check the returned document to see if the data you wanted to remove is still there.
Forget returning a document and trust that .update() always tells you the "truth" ;)
Which one of these you use depends on the application usage pattern, but those are the two different ways of returning an "reliable" result.
Bit of a Listing
So just to be sure, here's a listing that goes through all the examples and demonstrates what they actually return:
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
mongoose.connect('mongodb://localhost/test');
const branchesSchema = new Schema({
address: String,
isPrincipal: Boolean
});
const officeSchema = new Schema({
_id: Number,
name: String,
branches: [branchesSchema]
},{ _id: false });
const Office = mongoose.model('Office', officeSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
const testId = "5961a56d3ffd3d5e19c61610";
async.series(
[
// Clean data
(callback) =>
async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Insert some data and pull
(callback) =>
async.waterfall(
[
// Create and demonstrate
(callback) =>
Office.create({
_id: 1,
name: "My Office",
branches: [
{
address: "Some street, that avenue",
isPrincipal: true
},
{
address: "Another address",
isPrincipal: false
},
{
address: "Third address",
isPrincipal: false
}
]
},callback),
// Demo Alternates
(office,callback) =>
async.mapSeries(
[true,false].map((t,i) => ({ t, branch: office.branches[i] })),
(test,callback) =>
(test.t)
? Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": test.branch._id } } },
{ "new": true , "passRawResult": true },
(err,result,raw) => {
if (err) callback(err);
log(result);
log(raw);
callback();
})
: Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": test.branch._id } } },
{ "new": true } // false here
).then(result => {
log(result);
console.log(
"Present %s",
(result.branches.find( b =>
b._id.toHexString() === test.branch._id.toHexString() ))
? true : false
);
callback();
}).catch(err => callback(err)),
callback
)
],
callback
),
// Find and demonstate fails
(callback) =>
async.waterfall(
[
(callback) => Office.findOne({},callback),
(office,callback) =>
async.eachSeries([true,false],(item,callback) =>
(item)
? Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": testId } } },
{ "new": true, "passRawResult": true },
(err,result,raw) => {
if (err) callback(err);
log(result);
log(raw);
callback();
}
)
: Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": testId } } },
{ "new": true }
).then(result => {
console.log(result);
console.log(
"Present %s",
(result.branches.find( b =>
b._id.toHexString() === office.branches[0]._id.toHexString()))
? true : false
);
callback();
})
.catch(err => callback(err)),
callback)
],
callback
),
// Demonstrate update() modified shows 0
(callback) =>
Office.update(
{},
{ "$pull": { "branches": { "_id": testId } } }
).then(result => {
log(result);
callback();
})
.catch(err => callback(err)),
// Demonstrate wrapped promise
(callback) =>
Office.findOne()
.then(office => {
return new Promise((resolve,reject) => {
Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": testId } } },
{ "new": true, "passRawResult": true },
(err,result,raw) => {
if (err) reject(err);
resolve(raw)
}
);
})
})
.then(office => {
log(office);
callback();
})
.catch(err => callback(err))
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
);
And the output it produces:
Mongoose: offices.remove({}, {})
Mongoose: offices.insert({ _id: 1, name: 'My Office', branches: [ { address: 'Some street, that avenue', isPrincipal: true, _id: ObjectId("5961e5211a73e8331b44d74b") }, { address: 'Another address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d74a") }, { address: 'Third address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d749") } ], __v: 0 })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74b") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
"_id": 1,
"name": "My Office",
"__v": 0,
"branches": [
{
"address": "Another address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d74a"
},
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
]
}
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Another address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d74a"
},
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74a") } } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": 1,
"name": "My Office",
"__v": 0,
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
]
}
Present false
Mongoose: offices.findOne({}, { fields: {} })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
"_id": 1,
"name": "My Office",
"__v": 0,
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
]
}
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, upsert: false, remove: false, fields: {} })
{ _id: 1,
name: 'My Office',
__v: 0,
branches:
[ { address: 'Third address',
isPrincipal: false,
_id: 5961e5211a73e8331b44d749 } ] }
Present true
Mongoose: offices.update({}, { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, {})
{
"n": 1,
"nModified": 0,
"opTime": {
"ts": "6440680872013201413",
"t": 4
},
"electionId": "7fffffff0000000000000004",
"ok": 1
}
Mongoose: offices.findOne({}, { fields: {} })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
Finding and updating in two steps would better in this case, as you just said you'll have the option of warning the user.
A note on find. You have an array of objects branches. To match more than one field in find $elemMatch is needed. The query will look something like:
Office.findOne({_id: 1, "branches" : {$elemMatch: {"_id": body.branch.id, "isPrincipal": false}}})
Which will either return the office document or not. If it does, you proceed with findByIdAndUpdate (which is better than modifying and saving the already found document). If it does not, return a forbidden message to the user.
I browsed So many questions to find out logics for finding Index in a deeply nested array of object, I didn't find it useful for my requirement though.
in search of Solution in Javascript, Lodash/Underscore would be Fine too.
Let me just phrase out the Whole requirement, Hoping I get a path to find the solution for this issue.
Requirement:
I have an array of objects
arrObj =[
{
"id":3208,
"name":"List",
"issueResponses":[
],
"isActive":false
},
{
"id":3209,
"name":"Me",
"issueResponses":[
],
"isActive":false
},
{
"id":3314,
"name":"SNew",
"issueResponses":[
],
"isActive":false
},
{
"id":3315,
"name":"Olive",
"issueResponses":[
{
"id":3282,
"name":"related to Olive",
"issueResponses":[
],
"isActive":false
},
{
"id":3316,
"name":"My olives are not yet picked",
"issueResponses":[
{
"id":3317,
"name":"Pickup Not Done",
"issueResponses":[
],
"isActive":false
}
]
}
]
}
]
As we can see its deeply nested, I have another array
delValue = [3317,3282], And tomorrow it might be anything in these Deep Nesting.
I have to find these delValue arrays in arrObj and delete all those Objects which has "id" as these Values.
I am trying to solve this in a generic way which can support any number deep search Level and Deletion of the Object from that.
Please help me out in this, if More Information needed will be happy to provide.
You can use filter to achieve this:
function removeDeletions(array, deletion) {
return array.filter(el => {
if (Array.isArray(el.issueResponses)) el.issueResponses = removeDeletions(el.issueResponses, deletion);
return ! deletion.includes(el.id);
})
}
DEMO:
let arrObj = [{
"id": 3208,
"name": "List",
"issueResponses": [
],
"isActive": false
},
{
"id": 3209,
"name": "Me",
"issueResponses": [],
"isActive": false
},
{
"id": 3314,
"name": "SNew",
"issueResponses": [
],
"isActive": false
},
{
"id": 3315,
"name": "Olive",
"issueResponses": [{
"id": 3282,
"name": "related to Olive",
"issueResponses": [
],
"isActive": false
},
{
"id": 3316,
"name": "My olives are not yet picked",
"issueResponses": [{
"id": 3317,
"name": "Pickup Not Done",
"issueResponses": [
],
"isActive": false
}]
}
]
}
]
let delValue = [3317, 3282];
function removeDeletions(array, deletion) {
return array.filter(el => {
if (Array.isArray(el.issueResponses)) el.issueResponses = removeDeletions(el.issueResponses, deletion);
return !deletion.includes(el.id);
})
}
console.log(removeDeletions(arrObj, delValue));
Just keep checking issueResponses - like so:
function deleteIssueResponses(issues, ids) {
for (let i = issues.length; i >= 0; i--) {
if (issues[i].issueResponses.length) {
deleteIssueResponses(issues[i].issueResponses, ids);
}
if (ids.contains(issues[i].id)) {
issues.splice(i, 1);
}
}
}
And call it:
deleteIssueResponses(arrObj, [3317,3282]);
This should be quite simple to solve with some recursion. This function will delete the given ids in the array passed to it, or else call itself to do the same for the nested arrays.
function deleteIds(arr, ids) {
for (let i = 0; i < arr.length; i++) {
if (ids.indexOf(arr[i].id) !== -1) {
arr.splice(i, 1);
i--;
} else {
deleteIds(arr[i].issueResponses, ids);
}
}
}
Call with deleteIds(arrObj, delValue) as in your question.
We use object-scan for basic data processing tasks like this. Once you wrap your head around how to use it, it's pretty good and powerful. Here is how you could answer your questions:
// const objectScan = require('object-scan');
const prune = (values, data) => objectScan(['**[*].id'], {
rtn: 'count',
filterFn: ({ gparent, gproperty, value }) => {
if (values.includes(value)) {
gparent.splice(gproperty, 1);
return true;
}
return false;
}
})(data);
const arrObj = [{ id: 3208, name: 'List', issueResponses: [], isActive: false }, { id: 3209, name: 'Me', issueResponses: [], isActive: false }, { id: 3314, name: 'SNew', issueResponses: [], isActive: false }, { id: 3315, name: 'Olive', issueResponses: [{ id: 3282, name: 'related to Olive', issueResponses: [], isActive: false }, { id: 3316, name: 'My olives are not yet picked', issueResponses: [{ id: 3317, name: 'Pickup Not Done', issueResponses: [], isActive: false }] }] }];
console.log(prune([3317, 3282], arrObj)); // return number of deletes
// => 2
console.log(arrObj);
// => [ { id: 3208, name: 'List', issueResponses: [], isActive: false }, { id: 3209, name: 'Me', issueResponses: [], isActive: false }, { id: 3314, name: 'SNew', issueResponses: [], isActive: false }, { id: 3315, name: 'Olive', issueResponses: [ { id: 3316, name: 'My olives are not yet picked', issueResponses: [] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#15.0.0"></script>
Disclaimer: I'm the author of object-scan