Why is async.map passing only the value of my JSON? - json

I have a function in node.js that looks like this:
exports.getAllFlights = function(getRequest) {
// this is the package from npm called "async"
async.map(clients, getFlight, function(err, results) {
getRequest(results);
});
}
The variable clients should be a JSON that looks like this:
{'"A4Q"': 'JZA8187', "'B7P"': 'DAL2098' }.
I expect that the map function will pass the individual indices of the array of the variable clients to getFlight. However, instead it passed the values of that each(ex: 'DAL2098', 'JZA8187' and so on).
Is this the expected functionality? Is there a function in async that will do what I want?
The signature of getFlight is getFlight(identifier, callback). Identifier is what is currently messed up. It returns callback(null, rtn). Null reprsents the nonexistence of an error, rtn represents the JSON that my function produces.

Yes, that's the expected result. The documentation is not very clear but all iterating functions of async.js pass the values of the iterable, not the keys. There is the eachOf series of functions that pass both key and value. For example:
async.eachOf(clients, function (value, key, callback) {
// process each client here
});
Unfortunately there is no mapOf.
If you don't mind not doing things in parallel you can use eachOfSeries:
var results = [];
async.eachOfSeries(clients, function (value, key, callback) {
// do what getFlight needs to do and append to results array
}, function(err) {
getRequest(results);
});
Another (IMHO better) workaround is to use proper arrays:
var clients = [{'A4Q': 'JZA8187'},{'B7P': 'DAL2098'}];
Then use your original logic. However, I'd prefer to use a structure like the following:
var clients = [
{key: 'A4Q', val: 'JZA8187'},
{key: 'B7P', val: 'DAL2098'}
];

First create a custom event. Attach a listener for return data. then process it.
var EventEmitter = require('events');
var myEmitter = new EventEmitter();
myEmitter.emit('clients_data',{'"A4Q"': 'JZA8187'}); //emit your event where ever
myEmitter.on('clients_data', (obj) => {
if (typeof obj !=='undefined') {
if (obj.contructor === Object && Object.keys(obj).lenth == 0) {
console.log('empty');
} else {
for(var key in obj) {
var value = obj[key];
//do what you want here
}
}
}
});

Well, you need to format your clients object properly before you can use it with async.map(). Lodash _.map() can help you:
var client_list = _.map(clients, function(value, key) {
var item = {};
item[key] = value;
return item;
});
After that, you will have an array like:
[ { A4Q: 'JZA8187' }, { B7P: 'DAL2098' } ]
Then, you can use async.map():
exports.getAllFlights = function(getRequest) {
async.map(client_list, getFlight, function(err, results) {
getRequest(results);
});
};

Related

How to access the contents of a JSON file without a key?

Basically, I am setting up a web server via Node.js and Express (I am a beginner at this) to retrieve data by reading a JSON file.
For example, this is my data.json file:
[{
"color": "black",
"category": "hue",
"type": "primary"
},
{
"color": "red",
"category": "hue",
"type": "primary"
}
]
I am trying to retrieve all of the colors by implementing this code for it to display on localhost:
router.get('/colors', function (req, res) {
fs.readFile(__dirname + '/data.json', 'utf8', function (err, data) {
data = JSON.parse(data);
res.json(data); //this displays all of the contents of data.json
})
});
router.get('/colors:name', function (req, res) {
fs.readFile(__dirname + '/data.json', 'utf8', function (err, data) {
data = JSON.parse(data);
for (var i = 0; i < data.length; i++) {
res.json(data[i][1]); //trying to display the values of color
}
})
});
How do I go about doing this?
What you are trying to do is actually pretty simple once you break it into smaller problems. Here is one way to break it down:
Load your JSON data into memory for use by your API.
Define an API route which extracts only the colours from your JSON data and sends them to the client as a JSON.
var data = [];
try {
data = JSON.parse(fs.readFileSync('/path/to/json'));
} catch (e) {
// Handle JSON parse error or file not exists error etc
data = [{
"color": "black",
"category": "hue",
"type": "primary"
},
{
"color": "red",
"category": "hue",
"type": "primary"
}
]
}
router.get('/colors', function (req, res, next) {
var colors = data.map(function (item) {
return item.color
}); // This will look look like: ["black","red"]
res.json(colors); // Send your array as a JSON array to the client calling this API
})
Some improvements in this method:
The file is read only once synchronously when the application is started and the data is cached in memory for future use.
Using Array.prototype.map Docs to extract an array of colors from the object.
Note:
You can structure the array of colors however you like and send it down as a JSON in that structure.
Examples:
var colors = data.map(function(item){return {color:item.color};}); // [{"color":"black"},{"color":"red"}]
var colors = {colors: data.map(function(item){return item.color;})} // { "colors" : ["black" ,"red"] }
Some gotchas in your code:
You are using res.json in a for loop which is incorrect as the response should only be sent once. Ideally, you would build the JS object in the structure you need by iterating over your data and send the completed object once with res.json (which I'm guessing internally JSON.stringifys the object and sends it as a response after setting the correct headers)
Reading files is an expensive operation. If you can afford to read it once and cache that data in memory, it would be efficient (Provided your data is not prohibitively large - in which case using files to store info might be inefficient to begin with)
in express, you can do in this way
router.get('/colors/:name', (req, res) => {
const key = req.params.name
const content = fs.readFileSync(__dirname + '/data.json', 'utf8')
const data = JSON.parse(content)
const values = data.reduce((values, value) => {
values.push(value[key])
return values
}, [])
// values => ['black', 'red']
res.send(values)
});
and then curl http://localhost/colors/color,
you can get ['black', 'red']
What you're looking to do is:
res.json(data[i]['color']);
If you don't really want to use the keys in the json you may want to use the Object.values function.
...
data = JSON.parse(data)
var values = []
for (var i = 0; i < data.length; i++) {
values.push(Object.values(data[i])[0]) // 0 - color, 1 - category, 2 - type
}
res.json(values) // ["black","red"]
...
You should never use fs.readFileSync in production. Any sync function will block the event loop until the execution is complete hence delaying everything afterwords (use with caution if deemed necessary). A few days back I had the worst experience myself and learnt that in a hard way.
In express you can define a route with param or query and use that to map the contents inside fs.readFile callback function.
/**
* get color by name
*
* #param {String} name name of the color
* #return {Array} array of the color data matching param
*/
router.get('/colors/:name', (req, res) => {
const color = req.params.name
const filename = __dirname + '/data.json';
fs.readFile('/etc/passwd', 'utf8', (err, data) => {
if(err){
return res.send([]); // handle any error returned by readFile function here
}
try{
data = JSON.parse(data); // parse the JSON string to array
let filtered = []; // initialise empty array
if(data.length > 0){ // we got an ARRAY of objects, right? make your check here for the array or else any map, filter, reduce, forEach function will break the app
filtered = data.filter((obj) => {
return obj.color === color; // return the object if the condition is true
});
}
return res.send(filtered); // send the response
}
catch(e){
return res.send([]); // handle any error returned from JSON.parse function here
}
});
});
To summarise, use fs.readFile asynchronous function so that the event loop is not clogged up. Inside the callback parse through the content and then return the response. return is really important or else you might end up getting Error: Can't set headers after they are sent
DISCLAIMER This code above is untested but should work. This is just to demonstrate the idea.
I think you can’t access JSON without key. You can use Foreach loop for(var name : object){} check about foreach it may help you

AngularJS $http and filters

I have a JSON file, which contains:
{
"/default.aspx": "headerBg",
"/about.aspx": "aboutBg",
"/contact.aspx": "contactBg",
"/registration.aspx": "regBg",
"/clients.aspx": "clientsBg",
"/onlinesessions.aspx": "bg-white-box",
"/ondemamdsessions.aspx": "bg-grey"
}
Now I am reading this json file using $http, but I want to add a filter in below fashion:
Using window.location.pathname, I am reading path of the current page, suppose the current page is /about.aspx
Then I want to add a filter in $http response by which I want to read only aboutBg.
The code I wrote can retrieve all the values, but unable to filter that. Please help.
User this function where you receive the response.
function getPageBgClass(currentPage, responseData) {
if (responseData.hasOwnProperty(currentPage))
return responseData[currentPage]
else
return "none"
}
Here is how it should be used in your promise then function
function(response) {
var bg = getPageBgClass(window.location.pathname, response.data);
//Your code here ...
}
there is no direct method to get key using value from json.
you should make sure that there are no 2 keys having same value for below code to work
function swapJsonKeyValues(input) {
var one, output = {};
for (one in input) {
if (input.hasOwnProperty(one)) {
output[input[one]] = one;
}
}
return output;
}
var originaJSON = {
"/default.aspx": "headerBg",
"/about.aspx": "aboutBg",
"/contact.aspx": "contactBg",
"/registration.aspx": "regBg",
"/clients.aspx": "clientsBg",
"/onlinesessions.aspx": "bg-white-box",
"/ondemamdsessions.aspx": "bg-grey"
}
var invertedJSON = swapJsonKeyValues(originaJSON);
var samplepathname = "aboutBg";
var page = invertedJSON[samplepathname];
[function swapJsonKeyValues from https://stackoverflow.com/a/1970193/1006780 ]

Comparing user input to some fields in an array of JSON objects

I have a webserver with JSON data in it. This is what my data looks like
[
{
iduser: 1,
username: "joe",
password: "****"
},
{
iduser: 2,
username: "gina",
password: "****"
}
]
In my app I take some user input and wish to compare it to the username and password field. Here is where I check the data
.service('LoginService', function ($q, $http) {
return {
loginUser: function (name, pw) {
var deferred = $q.defer();
var promise = deferred.promise;
var user_data = $http.get("http://<my ip address>:<port>/login");
user_data.then(function ($scope, result) {
$scope.user = result.data;
})
for (var x in $scope.user) {
if (name == x.username && pw == x.password) {
deferred.resolve('Welcome ' + name + '!');
} else {
deferred.reject('Wrong credentials.');
}
}
promise.success = function (fn) {
promise.then(fn);
return promise;
}
promise.error = function (fn) {
promise.then(null, fn);
return promise;
}
return promise;
}
}
})
I am still learning angularJS and I know this is not a secure way to check the data I just want this loop to work.
My understanding of what I have here is that $scope.user holds my JSON data. Then the data is cycled through with the for loop and the user input name is compared to the field username of each iteration. But this is not the case as I am getting a fail every time.
I'm almost certain its a syntax error, but I don't know JavaScript or AngularJS well enough to find the problem. Any help is really appreciated, Thanks.
Edit 1
After what Nujabes said I made some changes since I don't need $scope.
//previous code the same
user_data.then(function (result) {
var user = result.data;
})
for (var x in user) {
if (name == x.username && pw == x.password) {
//prior code the same
I don't think var can hold the data and thats why I'm still getting errors. I think it should be in an array.
I think your syntax error is that you omit $scope.
You should inject $scope service to this line:
.service('LoginService',function($q,$http,$scope){ ...
});
And this code :
user_data.then(function ($scope, result) {
$scope.user = result.data;
});
Omit the $scope.
->
user_data.then(function (result) {
$scope.user = result.data;
});
like this.
Give it a try.
I hope it work.
(However, why do you want to use $scope service in your 'service'?
I think, defining local value and returning some method is a better choice.
and you use the $scope service in your 'controller'.)
$scope.user you are trying to loop through is array right ?
using (for/in) will store the key in the variable x which is in your case the index of each element (0,1,2,..) , to loop through arrays use (for/of) like this :
for (var value of array)
this will give you the values ...

Knockout: Mapping/binding JSON

I'm new to knockout. I'm trying to bind the json returned from ajax call but it isn't happening.
<script type="text/javascript">
var ServerData;
$(document).ready(function () {
ko.setTemplateEngine(new ko.nativeTemplateEngine);
var oViewModel = new CompanyModel();
oViewModel.Newitem(new Company("","","","","","","","","","","","","","",""));
ko.applyBindings(oViewModel);
BasicDatatable('#TableCompany');
});
function CompanyModel() {
var self = this;
self.CompanyList = ko.observable();
$.getJSON("getallcompanies", function (response) {
ServerData = ko.mapping.fromJSON(response);
self.CompanyList(ServerData);
});
}
In response I've the required data. But in ServerData I'm getting the following instead of the list.
function b() { if (0 < arguments.length) { if (!b.equalityComparer || !b.equalityComparer(d, arguments[0])) b.H(), d = arguments[0], b.G(); return this } r.T.Ha(b); return d }
Can You please let me know what's going wrong?
You should use ko.mapping.fromJS to bind JSON that comes from server.
Also, you don't need to define self.CompanyList = ko.observable(); and bind set value to that property in self.CompanyList(ServerData);.
It's all done by ko.mapping.fromJS. From Knockout Mapping documentation:
All properties of an object are converted into an observable. If an
update would change the value, it will update the observable.
Arrays are converted into observable arrays. If an update would
change the number of items, it will perform the appropriate
add/remove actions. It will also try to keep the order the same as
the original JavaScript array.
Try to change code like this:
function CompanyModel() {
var self = this;
$.getJSON("getallcompanies", function (response) {
ko.mapping.fromJSON(response, {}, self);
});
}

Using jQuery.when with array of deferred objects causes weird happenings with local variables

Let's say I have a site which saves phone numbers via an HTTP call to a service and the service returns the new id of the telephone number entry for binding to the telephone number on the page.
The telephones, in this case, are stored in an array called 'telephones' and datacontext.telephones.updateData sends the telephone to the server inside a $.Deferred([service call logic]).promise();
uploadTelephones = function (deffered) {
for (var i = 0; i < telephones.length; i++){
deffered.push(datacontext.telephones.updateData(telephones[i], {
success: function (response) {
telephones[i].telephoneId = response;
},
error: function () {
logger.error('Stuff errored');
}
}));
}
}
Now if I call:
function(){
var deferreds = [];
uploadTelephones(deferreds);
$.when.apply($, deferreds)
.then(function () {
editing(false);
complete();
},
function () {
complete();
});
}
A weird thing happens. All the telephones are sent back to the service and are saved. When the 'success' callback in uploadTelephones method is called with the new id as 'response', no matter which telephone the query relates to, the value of i is always telephones.length+1 and the line
telephones[i].telephoneId = response;
throws an error because telephones[i] does not exist.
Can anyone tell me how to keep the individual values of i in the success callback?
All of your closures (your anonymous functions capturing a variable in the local scope) refer to the same index variable, which will have the value of telephones.length after loop execution. What you need is to create a different variable for every pass through the for loop saving the value of i at the instance of creation at for later use.
To create a new different variable, the easiest way is to create an anonymous function with the code that is to capture the value at that particular place in the loop and immediately execute it.
either this:
for (var i = 0; i < telephones.length; i++)
{
(function () {
var saved = i;
deffered.push(datacontext.telephones.updateData(telephones[saved],
{
success: function (response)
{
telephones[saved].telephoneId = response;
},
error: function ()
{
logger.error('Stuff errored ');
}
}));
})();
}
or this:
for (var i = 0; i < telephones.length; i++)
{
(function (saved) {
deffered.push(datacontext.telephones.updateData(telephones[saved],
{
success: function (response)
{
telephones[saved].telephoneId = response;
},
error: function ()
{
logger.error('Stuff errored ');
}
}));
})(i);
}
should work.
Now, that's a bit ugly, though. Since you are already going through the process of executing an anonymous function over and over, if you want your code to be a little bit cleaner, you might want to look at Array.forEach and just use whatever arguments are passed in, or just use jQuery.each as you are already using jQuery.