I'm trying to get the value of eth0 nested inside this JSON object
"addresses": {
"eth0": [
"10.0.3.188"
]
},
I was using underscore.js to simplify the process
var _ = require('underscore')._;
var jsonData = {
"plays": [{
"play": {
"id": "d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",
"name": "lxc"
},
"tasks": [{
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": true,
"cmd": "lxc copy base \"bar69\"",
"delta": "0:00:01.417533",
"end": "2017-01-10 18:01:28.692981",
"invocation": {
"module_args": {
"_raw_params": "lxc copy base \"bar69\"",
"_uses_shell": true,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"warn": true
},
"module_name": "command"
},
"rc": 0,
"start": "2017-01-10 18:01:27.275448",
"stderr": "",
"stdout": "",
"stdout_lines": [],
"warnings": []
}
},
"task": {
"id": "297bf7b7-9ee7-4517-8763-bc3b15baa6e2",
"name": "clone from base"
}
},
{
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": true,
"cmd": "lxc config set \"bar69\" security.privileged true",
"delta": "0:00:00.053403",
"end": "2017-01-10 18:01:32.270750",
"invocation": {
"module_args": {
"_raw_params": "lxc config set \"bar69\" security.privileged true",
"_uses_shell": true,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"warn": true
},
"module_name": "command"
},
"rc": 0,
"start": "2017-01-10 18:01:32.217347",
"stderr": "",
"stdout": "",
"stdout_lines": [],
"warnings": []
}
},
"task": {
"id": "bc63ad6f-1808-48b8-a1de-729153d2b0c5",
"name": "Promote to privileged ct"
}
},
{
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_parsed": true,
"actions": [
"start"
],
"addresses": {
"eth0": [
"10.0.3.188"
]
},
"changed": true,
"invocation": {
"module_args": {
"architecture": null,
"cert_file": "/root/.config/lxc/client.crt",
"config": null,
"description": null,
"devices": null,
"ephemeral": null,
"force_stop": false,
"key_file": "/root/.config/lxc/client.key",
"name": "bar69",
"profiles": null,
"source": null,
"state": "started",
"timeout": 30,
"trust_password": null,
"url": "unix:/var/lib/lxd/unix.socket",
"wait_for_ipv4_addresses": true
},
"module_name": "lxd_container"
},
"log_verbosity": 0,
"old_state": "stopped"
}
},
"task": {
"id": "466c0da9-6cbf-4196-aea9-109218c3ed5f",
"name": "Start CT"
}
},
{
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_verbose_always": true,
"changed": false,
"invocation": {
"module_args": {
"msg": [
"10.0.3.188"
]
},
"module_name": "debug"
},
"msg": [
"10.0.3.188"
]
}
},
"task": {
"id": "978c490e-59c3-41d2-818d-ab4b557ad803",
"name": ""
}
}
]
}],
"stats": {
"lxc.server.com": {
"changed": 3,
"failures": 0,
"ok": 4,
"skipped": 0,
"unreachable": 0
}
}
}
This is what I have tried so far but no luck!
console.log(_.findKey(_.values(jsonData.tasks)));
Your help is highly appreciated
You could use a few functions like _.pluck() to get the objects at key host, _.property() to see if each host object has a key addresses, .map() to get a mapping of the values, ._filter() to see if a value was returned from the mapping, etc.:
var hosts = _.pluck(jsonData.plays[0].tasks, 'hosts');
var mapping = _.map(hosts, function(host) {
var keys = _.keys(host);
if (_.size(keys)) {
var nestedHost = host[_.first(keys)];
if (_.property('addresses')(nestedHost)) {
if (_.property('eth0')(nestedHost.addresses)) {
return nestedHost.addresses.eth0[0];
}
}
}
});
console.log(_.filter(mapping));
See it in action in this plunker, as well as the example below:
var jsonData = {
"plays": [{
"play": {
"id": "d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",
"name": "lxc"
},
"tasks": [{
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": true,
"cmd": "lxc copy base \"bar69\"",
"delta": "0:00:01.417533",
"end": "2017-01-10 18:01:28.692981",
"invocation": {
"module_args": {
"_raw_params": "lxc copy base \"bar69\"",
"_uses_shell": true,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"warn": true
},
"module_name": "command"
},
"rc": 0,
"start": "2017-01-10 18:01:27.275448",
"stderr": "",
"stdout": "",
"stdout_lines": [],
"warnings": []
}
},
"task": {
"id": "297bf7b7-9ee7-4517-8763-bc3b15baa6e2",
"name": "clone from base"
}
}, {
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": true,
"cmd": "lxc config set \"bar69\" security.privileged true",
"delta": "0:00:00.053403",
"end": "2017-01-10 18:01:32.270750",
"invocation": {
"module_args": {
"_raw_params": "lxc config set \"bar69\" security.privileged true",
"_uses_shell": true,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"warn": true
},
"module_name": "command"
},
"rc": 0,
"start": "2017-01-10 18:01:32.217347",
"stderr": "",
"stdout": "",
"stdout_lines": [],
"warnings": []
}
},
"task": {
"id": "bc63ad6f-1808-48b8-a1de-729153d2b0c5",
"name": "Promote to privileged ct"
}
}, {
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_parsed": true,
"actions": [
"start"
],
"addresses": {
"eth0": [
"10.0.3.188"
]
},
"changed": true,
"invocation": {
"module_args": {
"architecture": null,
"cert_file": "/root/.config/lxc/client.crt",
"config": null,
"description": null,
"devices": null,
"ephemeral": null,
"force_stop": false,
"key_file": "/root/.config/lxc/client.key",
"name": "bar69",
"profiles": null,
"source": null,
"state": "started",
"timeout": 30,
"trust_password": null,
"url": "unix:/var/lib/lxd/unix.socket",
"wait_for_ipv4_addresses": true
},
"module_name": "lxd_container"
},
"log_verbosity": 0,
"old_state": "stopped"
}
},
"task": {
"id": "466c0da9-6cbf-4196-aea9-109218c3ed5f",
"name": "Start CT"
}
}, {
"hosts": {
"lxc.server.com": {
"_ansible_no_log": false,
"_ansible_verbose_always": true,
"changed": false,
"invocation": {
"module_args": {
"msg": [
"10.0.3.188"
]
},
"module_name": "debug"
},
"msg": [
"10.0.3.188"
]
}
},
"task": {
"id": "978c490e-59c3-41d2-818d-ab4b557ad803",
"name": ""
}
}]
}],
"stats": {
"lxc.server.com": {
"changed": 3,
"failures": 0,
"ok": 4,
"skipped": 0,
"unreachable": 0
}
}
};
document.addEventListener('DOMContentLoaded', function() {
var hosts = _.pluck(jsonData.plays[0].tasks, 'hosts');
var mapping = _.map(hosts, function(host) {
var keys = _.keys(host);
if (_.size(keys)) {
var nestedHost = host[_.first(keys)];
if (_.property('addresses')(nestedHost)) {
if (_.property('eth0')(nestedHost.addresses)) {
return nestedHost.addresses.eth0[0];
}
}
}
});
document.getElementById('console').innerHTML = _.filter(mapping);
});
<script data-require="underscore.js#*" data-semver="1.8.3" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Address(es):
<div id="console"></div>
You can do this without underscore. An approach you can take is take the given property string and a task, split the property based on the deliminator (you cannot use '.' because you have dotted properties like 'lxc.server.com'), and recursively examine the object until you find the value (or not).
Note: The solutions bellow assume you want to pass a delimited property string but you can just as easily pass an array of props directly and not wrap the helper function.
Tail-cail recursive solution
var jsonData={plays:[{play:{id:"d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",name:"lxc"},tasks:[{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc copy base "bar69"',delta:"0:00:01.417533",end:"2017-01-10 18:01:28.692981",invocation:{module_args:{_raw_params:'lxc copy base "bar69"',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:27.275448",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"297bf7b7-9ee7-4517-8763-bc3b15baa6e2",name:"clone from base"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc config set "bar69" security.privileged true',delta:"0:00:00.053403",end:"2017-01-10 18:01:32.270750",invocation:{module_args:{_raw_params:'lxc config set "bar69" security.privileged true',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:32.217347",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"bc63ad6f-1808-48b8-a1de-729153d2b0c5",name:"Promote to privileged ct"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,actions:["start"],addresses:{eth0:["10.0.3.188"]},changed:!0,invocation:{module_args:{architecture:null,cert_file:"/root/.config/lxc/client.crt",config:null,description:null,devices:null,ephemeral:null,force_stop:!1,key_file:"/root/.config/lxc/client.key",name:"bar69",profiles:null,source:null,state:"started",timeout:30,trust_password:null,url:"unix:/var/lib/lxd/unix.socket",wait_for_ipv4_addresses:!0},module_name:"lxd_container"},log_verbosity:0,old_state:"stopped"}},task:{id:"466c0da9-6cbf-4196-aea9-109218c3ed5f",name:"Start CT"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_verbose_always:!0,changed:!1,invocation:{module_args:{msg:["10.0.3.188"]},module_name:"debug"},msg:["10.0.3.188"]}},task:{id:"978c490e-59c3-41d2-818d-ab4b557ad803",name:""}}]}],stats:{"lxc.server.com":{changed:3,failures:0,ok:4,skipped:0,unreachable:0}}};
function getNestedPropHelper(obj, [first, ...rest]) {
// base case
if (typeof obj !== 'object' || !obj) return undefined;
return rest.length === 0 // if we only have one property
? obj[first] // return the value
: getNestedPropHelper(obj[first], rest); // otherwise recursively return the rest
}
function getNestedProp(obj, prop, delim = '|') {
return getNestedPropHelper(obj, prop.split(delim));
}
// extract the tasks
const tasks = jsonData.plays.reduce((arr, play) => arr.concat(play.tasks), []);
// get the eth0 property for each task
const props = tasks.map(task =>
getNestedProp(task, 'hosts|lxc.server.com|addresses|eth0')
);
// log eth0 properties for each task (only the third one actually has the value)
console.log(props);
You can also do this iteratively, which should usually be faster (although not much in environments that support tail-calls):
Iterative solution
var jsonData={plays:[{play:{id:"d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",name:"lxc"},tasks:[{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc copy base "bar69"',delta:"0:00:01.417533",end:"2017-01-10 18:01:28.692981",invocation:{module_args:{_raw_params:'lxc copy base "bar69"',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:27.275448",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"297bf7b7-9ee7-4517-8763-bc3b15baa6e2",name:"clone from base"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc config set "bar69" security.privileged true',delta:"0:00:00.053403",end:"2017-01-10 18:01:32.270750",invocation:{module_args:{_raw_params:'lxc config set "bar69" security.privileged true',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:32.217347",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"bc63ad6f-1808-48b8-a1de-729153d2b0c5",name:"Promote to privileged ct"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,actions:["start"],addresses:{eth0:["10.0.3.188"]},changed:!0,invocation:{module_args:{architecture:null,cert_file:"/root/.config/lxc/client.crt",config:null,description:null,devices:null,ephemeral:null,force_stop:!1,key_file:"/root/.config/lxc/client.key",name:"bar69",profiles:null,source:null,state:"started",timeout:30,trust_password:null,url:"unix:/var/lib/lxd/unix.socket",wait_for_ipv4_addresses:!0},module_name:"lxd_container"},log_verbosity:0,old_state:"stopped"}},task:{id:"466c0da9-6cbf-4196-aea9-109218c3ed5f",name:"Start CT"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_verbose_always:!0,changed:!1,invocation:{module_args:{msg:["10.0.3.188"]},module_name:"debug"},msg:["10.0.3.188"]}},task:{id:"978c490e-59c3-41d2-818d-ab4b557ad803",name:""}}]}],stats:{"lxc.server.com":{changed:3,failures:0,ok:4,skipped:0,unreachable:0}}};
function getNestedPropHelper(obj, props) {
const isObject = (obj) => typeof obj === 'object' && obj;
if (!isObject(obj)) return undefined;
// keep extracting the properties
for (const prop of props) {
obj = obj[prop];
// of we come across a non-object property before we're done, the property path is invalid
if (!isObject(obj)) return undefined;
}
// if we reached this point, we found the value
return obj;
}
function getNestedProp(obj, prop, delim = '|') {
return getNestedPropHelper(obj, prop.split(delim));
}
// extract the tasks
const tasks = jsonData.plays.reduce((arr, play) => arr.concat(play.tasks), []);
// get the eth0 property for each task
const props = tasks.map(task =>
getNestedProp(task, 'hosts|lxc.server.com|addresses|eth0')
);
// log eth0 properties for each task (only the third one actually has the value)
console.log(props);
Curried recursive solution same approach can be taken for the iterative one
You could also curry the function so you can build getters for certain property paths.
var jsonData={plays:[{play:{id:"d10aae34-6713-4e14-8ad5-fa2fbf6aa2b5",name:"lxc"},tasks:[{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc copy base "bar69"',delta:"0:00:01.417533",end:"2017-01-10 18:01:28.692981",invocation:{module_args:{_raw_params:'lxc copy base "bar69"',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:27.275448",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"297bf7b7-9ee7-4517-8763-bc3b15baa6e2",name:"clone from base"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,changed:!0,cmd:'lxc config set "bar69" security.privileged true',delta:"0:00:00.053403",end:"2017-01-10 18:01:32.270750",invocation:{module_args:{_raw_params:'lxc config set "bar69" security.privileged true',_uses_shell:!0,chdir:null,creates:null,executable:null,removes:null,warn:!0},module_name:"command"},rc:0,start:"2017-01-10 18:01:32.217347",stderr:"",stdout:"",stdout_lines:[],warnings:[]}},task:{id:"bc63ad6f-1808-48b8-a1de-729153d2b0c5",name:"Promote to privileged ct"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_parsed:!0,actions:["start"],addresses:{eth0:["10.0.3.188"]},changed:!0,invocation:{module_args:{architecture:null,cert_file:"/root/.config/lxc/client.crt",config:null,description:null,devices:null,ephemeral:null,force_stop:!1,key_file:"/root/.config/lxc/client.key",name:"bar69",profiles:null,source:null,state:"started",timeout:30,trust_password:null,url:"unix:/var/lib/lxd/unix.socket",wait_for_ipv4_addresses:!0},module_name:"lxd_container"},log_verbosity:0,old_state:"stopped"}},task:{id:"466c0da9-6cbf-4196-aea9-109218c3ed5f",name:"Start CT"}},{hosts:{"lxc.server.com":{_ansible_no_log:!1,_ansible_verbose_always:!0,changed:!1,invocation:{module_args:{msg:["10.0.3.188"]},module_name:"debug"},msg:["10.0.3.188"]}},task:{id:"978c490e-59c3-41d2-818d-ab4b557ad803",name:""}}]}],stats:{"lxc.server.com":{changed:3,failures:0,ok:4,skipped:0,unreachable:0}}};
function getNestedPropHelper(obj, [first, ...rest]) {
// base case
if (typeof obj !== 'object' || !obj) return undefined;
return rest.length === 0 // if we only have one property
? obj[first] // return the value
: getNestedPropHelper(obj[first], rest); // otherwise recursively return the rest
}
function getNestedProp(prop, delim = '|') {
const props = prop.split(delim);
return function(obj) {
return getNestedPropHelper(obj, props);
}
}
// now you have a getter that will extract eth0 for any task
const getEth0 = getNestedProp('hosts|lxc.server.com|addresses|eth0');
// extract the tasks
const tasks = jsonData.plays.reduce((arr, play) => arr.concat(play.tasks), []);
// extract eth0 from each task
const props = tasks.map(getEth0);
// log eth0 properties for each task (only the third one actually has the value)
console.log(props);
If you want to access it directly, try the following:
jsonData.plays[0].tasks[2].hosts["lxc.server.com"].addresses.eth0[0]
Edit: This was tested. The post that has tasks[3] is incorrect.
We use object-scan for many data processing tasks. It's powerful and fast once you wrap your head around it. Here is how you could solve your question
// const objectScan = require('object-scan');
const find = (input) => objectScan(['**.addresses.eth0[0]'], {
abort: true,
rtn: 'value'
})(input);
const jsonData = { plays: [{ play: { id: 'd10aae34-6713-4e14-8ad5-fa2fbf6aa2b5', name: 'lxc' }, tasks: [{ hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_parsed: true, changed: true, cmd: 'lxc copy base "bar69"', delta: '0:00:01.417533', end: '2017-01-10 18:01:28.692981', invocation: { module_args: { _raw_params: 'lxc copy base "bar69"', _uses_shell: true, chdir: null, creates: null, executable: null, removes: null, warn: true }, module_name: 'command' }, rc: 0, start: '2017-01-10 18:01:27.275448', stderr: '', stdout: '', stdout_lines: [], warnings: [] } }, task: { id: '297bf7b7-9ee7-4517-8763-bc3b15baa6e2', name: 'clone from base' } }, { hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_parsed: true, changed: true, cmd: 'lxc config set "bar69" security.privileged true', delta: '0:00:00.053403', end: '2017-01-10 18:01:32.270750', invocation: { module_args: { _raw_params: 'lxc config set "bar69" security.privileged true', _uses_shell: true, chdir: null, creates: null, executable: null, removes: null, warn: true }, module_name: 'command' }, rc: 0, start: '2017-01-10 18:01:32.217347', stderr: '', stdout: '', stdout_lines: [], warnings: [] } }, task: { id: 'bc63ad6f-1808-48b8-a1de-729153d2b0c5', name: 'Promote to privileged ct' } }, { hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_parsed: true, actions: ['start'], addresses: { eth0: ['10.0.3.188'] }, changed: true, invocation: { module_args: { architecture: null, cert_file: '/root/.config/lxc/client.crt', config: null, description: null, devices: null, ephemeral: null, force_stop: false, key_file: '/root/.config/lxc/client.key', name: 'bar69', profiles: null, source: null, state: 'started', timeout: 30, trust_password: null, url: 'unix:/var/lib/lxd/unix.socket', wait_for_ipv4_addresses: true }, module_name: 'lxd_container' }, log_verbosity: 0, old_state: 'stopped' } }, task: { id: '466c0da9-6cbf-4196-aea9-109218c3ed5f', name: 'Start CT' } }, { hosts: { 'lxc.server.com': { _ansible_no_log: false, _ansible_verbose_always: true, changed: false, invocation: { module_args: { msg: ['10.0.3.188'] }, module_name: 'debug' }, msg: ['10.0.3.188'] } }, task: { id: '978c490e-59c3-41d2-818d-ab4b557ad803', name: '' } }] }], stats: { 'lxc.server.com': { changed: 3, failures: 0, ok: 4, skipped: 0, unreachable: 0 } } };
console.log(find(jsonData));
// => 10.0.3.188
.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
you can use this code to loop on object properties .
for (key in jsonData){
if(key == "eth0" ){
console.log(jsonData[key].toString())
}
}
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
}
]
}
]
}
I looked at several of the suggested solutions but none seemed to rise to this confounding data formatting challenge.
I have a huge JSON file (over 100k rows) and massive duplicates of data all as top level objects. Here's an example:
[
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326B",
"chipset":"Mediatek MT6853V/NZA",
"date":"2022-01-01",
"fw_id":"A326BXXS4AVA1",
"android":"R(Android 11)",
"known_passcode":false,
"afu":false,
"bfu":false,
"bruteforce":false
},
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326U",
"chipset":"Mediatek MT6853V/NZA",
"date":"2021-03-01",
"fw_id":"A326USQU1AUD4",
"android":"R(Android 11)",
"known_passcode":true,
"afu":false,
"bfu":true,
"bruteforce":true
},
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326U1",
"chipset":"Mediatek MT6853V/NZA",
"date":"2021-09-01",
"fw_id":"A326U1UEU5AUJ2",
"android":"R(Android 11)",
"known_passcode":true,
"afu":false,
"bfu":true,
"bruteforce":true
},
{
"manufacturer":"LGE",
"device":"LG K31",
"model":"LGL355DL",
"chipset":"Mediatek MT6762",
"date":"unknown",
"fw_id":"L355DL10l",
"android":"unknown",
"known_passcode":false,
"afu":false,
"bfu":false,
"bruteforce":false
}
]
This needs to be organized so that data points like manufacturer, device, model are not duplicated hundreds of times.
Btw, here's a JSFiddle to play with:
https://jsfiddle.net/xpancom/Lq7duahv/
Ideally, the JSON format would be the following:
[
{
"manufacturers": [
{
"manufacturer": "Samsung",
"devices": [
{
"device": "Galaxy A32 5G",
"models": [
{
"model": "SM-A326B",
"data": [
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2022-01-01",
"fw_id": "A326BXXS4AVA1",
"android": "R(Android 11)",
"known_passcode": false,
"afu": false,
"bfu": false,
"bruteforce": false
},
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2021-09-01",
"fw_id": "A326BXXU3AUH7",
"android": "R(Android 11)",
"known_passcode": true,
"afu": false,
"bfu": true,
"bruteforce": true
}
]
},
{
"model": "SM-A326U1",
"data": [
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2021-09-01",
"fw_id": "A326U1UEU5AUJ2",
"android": "R(Android 11)",
"known_passcode": true,
"afu": false,
"bfu": true,
"bruteforce": true
}
]
}
]
}
]
},
{
"manufacturer": "LGE",
"devices": [
{
"device": "LG K31",
"models": [
{
"model": "SM-A326B",
"data": [
{
"chipset": "Mediatek MT6762",
"date": "unknown",
"fw_id": "L355DL10l",
"android": "unknown",
"known_passcode": false,
"afu": false,
"bfu": false,
"bruteforce": false
}
]
}
]
}
]
}
]
}
]
Working in React, here's what I've got so far in trying to massage this data:
const source = data;
const destination = [];
const classifiedTokens = []; // will be used to stored already classified tokens
const classifiedTokensModel = []; // will be used to stored already classified tokens for models
const getNextTokenArray = (source) => {
let unusedToken = null;
const nextTokenArray = source.filter(function (element) {
if (!unusedToken && !classifiedTokens.includes(element['device'])) {
unusedToken = element['device'];
classifiedTokens.push(unusedToken);
}
return unusedToken ? unusedToken === element['device'] : false;
});
return unusedToken ? nextTokenArray : null;
};
// Pass in arrays deconstructed from addToDestination to process third tier nested objects for models
const getNextTokenArrayModel = (tokenObject) => {
let tokenObjectDevice = tokenObject['device'];
let tokenObjectData = tokenObject['data'];
let unusedTokenModel = null;
const nextTokenArrayModel = tokenObjectData.filter(function (element) {
if (!unusedTokenModel && !classifiedTokensModel.includes(element['model'])) {
unusedTokenModel = element['model'];
classifiedTokensModel.push(unusedTokenModel);
}
return unusedTokenModel ? unusedTokenModel === element['model'] : false;
});
//return unusedTokenModel ? nextTokenArrayModel : null;
if (unusedTokenModel) {
if (nextTokenArrayModel.length === 0) return;
let res = {
device: tokenObjectDevice,
model: nextTokenArrayModel[0]['model'],
data: [],
};
nextTokenArrayModel.forEach((element) => {
res.data.push({
manufacturer: element.manufacturer,
chipset: element.chipset,
date: element.date,
fw_id: element.fw_id,
android: element.android,
knownPasscode: element.knownPasscode,
afu: element.afu,
bfu: element.bfu,
bruteforce: element.bruteforce,
});
});
destination.push(res);
} else {
return null;
}
};
const addToDestination = (tokenArray) => {
if (tokenArray.length === 0) return;
let res = {
device: tokenArray[0]['device'],
data: [],
};
tokenArray.forEach((element) => {
res.data.push({
manufacturer: element.manufacturer,
model: element.model,
chipset: element.chipset,
date: element.date,
fw_id: element.fw_id,
android: element.android,
knownPasscode: element.knownPasscode,
afu: element.afu,
bfu: element.bfu,
bruteforce: element.bruteforce,
});
});
getNextTokenArrayModel(res); // Call this to process and group nested model duplicates by device
//destination.push(res);
};
let nextTokenArray = getNextTokenArray(source);
while (nextTokenArray) {
addToDestination(nextTokenArray);
nextTokenArray = getNextTokenArray(source);
}
setTimeout(() => {
document.getElementById('root').innerHTML =
'<pre>' + JSON.stringify(destination, null, 2) + '</pre>';
}, 1000);
};
And here's the JSFiddle again:
https://jsfiddle.net/xpancom/Lq7duahv/
Who can smash this data formatting dilemma?
This answer is not React specific, but one approach would be to use array.reduce() to transform each level/node of the structure as shown in the code snippet below.
const source = [
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326B',
chipset: 'Mediatek MT6853V/NZA',
date: '2022-01-01',
fw_id: 'A326BXXS4AVA1',
android: 'R(Android 11)',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326B',
chipset: 'Mediatek MT6853V/NZA',
date: '2022-01-01',
fw_id: 'A326BXXS4AVA1',
android: 'R(Android 11)',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326U',
chipset: 'Mediatek MT6853V/NZA',
date: '2021-03-01',
fw_id: 'A326USQU1AUD4',
android: 'R(Android 11)',
known_passcode: true,
afu: false,
bfu: true,
bruteforce: true,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326U1',
chipset: 'Mediatek MT6853V/NZA',
date: '2021-09-01',
fw_id: 'A326U1UEU5AUJ2',
android: 'R(Android 11)',
known_passcode: true,
afu: false,
bfu: true,
bruteforce: true,
},
{
manufacturer: 'LGE',
device: 'LG K31',
model: 'LGL355DL',
chipset: 'Mediatek MT6762',
date: 'unknown',
fw_id: 'L355DL10l',
android: 'unknown',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
];
function generateTree(data, key) {
return data.reduce((acc, val) => {
// Split the key name from the child data
const { [key.name]: keyName, ...childData } = val;
// Find a tree item in the structure being generated
const treeItem = acc.find((item) => item[key.name] === keyName);
if (treeItem) {
// If found, append child data
treeItem[key.child].push(childData);
} else {
// If not found, create new key and append child data
acc.push({ [key.name]: keyName, [key.child]: [childData] });
}
return acc;
}, []);
}
// Generate manufacturer/device structure
const manufacturers = generateTree(source, {
name: 'manufacturer', // Key name to use as grouping identifier
child: 'devices', // Key name for child data
});
// Generate device/model structure
manufacturers.forEach((manufacturer) => {
manufacturer.devices = generateTree(manufacturer.devices, {
name: 'device',
child: 'models',
});
// Generate model/data structure
manufacturer.devices.forEach((device) => {
device.models = generateTree(device.models, {
name: 'model',
child: 'data',
});
});
});
const destination = [{ manufacturers }];
console.log(destination);
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.
var levels= [
{
path: 'RS',
hasChild :true
},
{
path: 'MO',
hasChild: true
},
{
path: 'EL',
hasChild: true
},
{
path: 'CL',
hasChild: false
},
{
path: 'EL1',
hasChild: true
},
{
path: 'CL1',
hasChild: false
},
{
path: 'RS2',
hasChild :true
},
{
path: 'MO2',
hasChild: true
},
{
path: 'EL2',
hasChild: true
},
{
path: 'CL2',
hasChild: false
},
{
path: 'CL3',
hasChild: false
},
];
Is it possible to create complete path from the object 'level' using underscore.js?
For e.g. - RS\MO\EL\CL
RS\MO\EL1\CL1
RS2\MO2\EL2\CL2
RS2\MO2\CL3\CL3
In any of the above levels child can appear more than one. Please advise if underscore.js can do deep watching of nested array of objects.
Please apologize me for the bad formatting of nested array of objects above.
function parse (levels) {
var buffer = [], target = [];
levels.forEach(function (level) {
buffer.push(level.path);
if (!level.hasChild) {
target.push(buffer.join('/'));
buffer.splice(0, buffer.length + 1);
}
});
return levels;
}
Gives: [ 'RS/MO/EL/CL', 'EL1/CL1', 'RS2/MO2/EL2/CL2', 'CL3' ]
Given your current structure, the logic to get the desired output is unclear.
How should the program know that RS2 starts a new node, but EL1 doesn't?
EDIT:
This solve the problem, but honestly, its hacky. A better way is to structure the data in a better way.
function parse (levels) {
var buffer = [], target = [];
levels.forEach(function (level) {
if (level.hasChild) {
buffer.push(level.path);
}
else {
var tmp = buffer.slice();
tmp.push(level.path);
target.push(tmp.join('/'));
buffer.splice(buffer.length - 1, 1);
}
if (/^RS/.test(level.path)) {
buffer.splice(1, buffer.length);
}
});
return target;
}
Result: ['RS/MO/EL/CL', 'RS/MO/EL1/CL1', 'RS/MO2/EL2/CL2', 'RS/MO2/CL3']
use _.each for that
var path =""
_.each(levels,function(object){
path = path +object.path+"/"
})
console.log(path)
Output:"RS/MO/EL/CL/EL1/CL1/RS2/MO2/EL2/CL2/CL3/"
Edit:
I think your Json shoud be like this:
var levels = [
{
"path": "RS",
"hasChild": true,
"childerens": {
"path": "MO",
"hasChild": true,
"childe1": [
{
"path": "EL",
"hasChild": true
},
{
"path": "CL",
"hasChild": false
}
],
"childe2": [
{
"path": "EL1",
"hasChild": true
},
{
"path": "CL1",
"hasChild": false
}
]
},
"isParent": true
},
{
"path": "RS2",
"hasChild": true,
"childerens": {
"path": "MO2",
"hasChild": true,
"chiled1": [
{
"path": "EL2",
"hasChild": true
},
{
"path": "CL2",
"hasChild": false
}
],
"chiled2": [
{
"path": "CL3",
"hasChild": true
},
{
"path": "CL3",
"hasChild": false
}
]
},
"isParent": true
}
]
For validate json is wrong or right go to JSONLINT
Remove variable name for validation