Check JSON data structure for existance of lower level names - javascript

Can I use 'in' to check existence of non top level names in a JSON data structure in a single comparison?
I have n tier JSON data structures,
I can do: if("mbled" in jsonData), works fine
For a lower tier name:
I can do this but (works but gets clunky as I go deeper): if("pnpenvsense1" in jsonData && "light" in jsonData.pnpenvsense1)
I'd prefer something like (doesn't work, always returns false): if("pnpenvsense1.light" in jsonData)

something like:
function objExists(path, struct){
path = path.split('.');
for(var i=0, l=path.length; i<l; i++){
if(!struct.hasOwnProperty(path[i])){ return false; }
struct = struct[path[i]];
}
return true;
}
objExists('pnpenvsense1.light', jsonData);

try
// will check if it is defined and not false
if(pnpenvsense1.light !== undefined && pnpenvsense1.light != false )
{ // my code }
http://jsfiddle.net/
arr = new Array();
arr['myKey'] = 12;
arr['myOtherKey'] = { "first" : "yes" , "second" : false };
if(arr.myKey !== undefined && arr.myKey != false)
alert("1 -> " + arr.myKey);
if(arr.myOtherKey.first !== undefined && arr.myOtherKey.first != false)
alert("2 -> " + arr.myOtherKey.first);

Related

How do I handle indexOf returning 'null' without using try/catch(err)?

I'm populating a table with data - using fixed-data-table, which is a React.js component. However, that isn't so important at this stage.
The table has a search box where the issue stems from.
First, here's the interesting part of the code.
for (var index = 0; index < size; index++) {
if (!filterBy || filterBy == undefined) {
filteredIndexes.push(index);
}
else {
var backendInfo = this._dataList[index];
var userListMap = hostInfo.userList;
var userListArr = Object.values(userListMap);
function checkUsers(){
for (var key in userListArr) {
if (userListArr.hasOwnProperty(key) && userListArr[key].text.toLowerCase().indexOf(filterBy) !== -1) {
return true;
}
}
return false;
}
if (backendInfo.firstName.indexOf(filterBy) !== -1 || backendInfo.lastName.toLowerCase().indexOf(filterBy) !== -1 || backendInfo.countryOrigin.toLowerCase().indexOf(filterBy) !== -1
|| backendInfo.userListMap.indexOf(filterBy) !== -1) {
filteredIndexes.push(index);
}
}
}
This is rendered and the last part is throwing errors if you input something in the table, and a column returns null from the user input.
The thing is, I can make the code work if I change the last part to ..
try {
if (backendInfo.firstName.indexOf(filterBy) !== -1 || backendInfo.lastName.toLowerCase().indexOf(filterBy) !== -1 || backendInfo.countryOrigin.toLowerCase().indexOf(filterBy) !== -1
|| backendInfo.userListMap.indexOf(filterBy) !== -1) {
filteredIndexes.push(index);
}
}
catch(err) {
console.log('Exception')
}
With the try/catch, it works 100% as intended and handles the indexOf returning null... But this can't be the way to properly handle it - I'm assuming this sort of exception handling is, well, supposed to be for rare exceptions, and shouldn't really be used on the front-end as much as the backend.
How do I handle indexOf returning null in the above Javascript code? It might return null in any of the sources columns that are being populated.
If a key cannot be found, JS will throw an error. Try-catch is a good way to fix these errors, but there is an alternative:
You could check if keys exist in an object prior to pushing a value into it.
var data = { };
var key = "test";
// your method works great
try {
var value = data.firstname.indexOf(key);
} catch (err) {}
// another method, I'd prefer the try/catch
var value = data.firstname ? data.firstname.indexOf(key) : undefined;
// test if the object is the type of object you are looking for
// this is in my opinion the best option.
if(data.firstname instanceof Array){
var value = data.firstname.indexOf(key);
}
// you can use the last option in your code like this:
var firstnameHasKey = data.firstname instanceof Array && ~data.firstname.indexOf(key);
var lastnameHasKey = data.lastname instanceof Array && ~data.lastname.indexOf(key);
if(firstnameHasKey || lastnameHasKey){
// logics
}
If you test the instanceof && indexOf, there will never be an error. If firstname is undefined, the indexOf will never be checked.
Ofcourse you can use this for other types:
var myDate = new Date();
myDate instanceof Date; // returns true
myDate instanceof Object; // returns true
myDate instanceof String; // returns false
MDN documentation

Return an empty array if string == ""

I'm rewriting my old table sort library. In the library, I have this method which gets some attributes from the table:
<table data-sort-cols="" data-sort-orders="">
getSortInfo : function(element) {
switch (element.tagName) {
case "TABLE":
function getSortAttr(element, attr) {
var info = element.getAttribute(attr);
if (info != "") {
info = info.split(",").map(function(val) { return parseInt(val,10) });
} else {
info = [];
}
return info;
};
return {
columns : getSortAttr(element, "data-sort-cols"),
orders : getSortAttr(element, "data-sort-orders"),
}
I want to shorten the getSortAttr function. Function returns the assigned value (e.g. "1,3,2" -> [1,3,2]). If there is not an assigned value to the attribute, it returns an empty array. I want to get rid of the if statement.
Is there a way to modify this line, and make it return an empty array if the string is empty ""?
info = info.split(",").map(function(val) { return parseInt(val,10) });
I've tried
// returns [NaN]
"".split(",").map(function(val) {
return parseInt(val, 10);
})
// returns [undefined]
"".split(",").map(function(val) {
var num = parseInt(val, 10);
if (!isNaN(num) && isFinite(num)) {
return num;
}
})
but didn't work.
The following function will check if info == "", if info == "", it will then return an empty array, if not, it will split the info string and map it. This is called a shorthand if statement.
info = (info == "") ? [] : info.split(",").map(function(val) { return parseInt(val,10) });
You can also add more conditions to the shorthand condition, e.g:
(info == "" || info != undefined || info != null)
If the above info == "" doesn't work, this might mean that info is undefined. To combat this we can use the following statement:
(typeof(info) != undefined)
You can use
function getSortAttr(element, attr) {
return (element.getAttribute(attr) || "").split(",").filter(Boolean).map(Number);
}
to filter out empty strings from the array before mapping them to numbers.

|| operator not setting default when null

I am running this line:
var good = data["good"] || false;
where data comes from the success method in a jquery ajax request.
But, what I thought that this would do is default good to false if data["good"] is null, but it is not.
Chrome is throwing this:
Uncaught TypeError: Cannot read property 'good' of null
and since it is null, shouldn't good then be set to false?
The problem is not that data["good"] is null, but that data itself is null.
Your code as is would be fine if data always has a value, but may not have property good. But unfortunately the JavaScript engine doesn't check if everything in a statement is undefined or null, i.e. it won't test data, and then test data["good"] and so on.
You need to test if data has at least some sort of value first, that is, it is "truthy".
Fix 1
You can lay it out clearly like so:
var good = false;
if(data && data["good"]) {
good = data["good"];
}
Fix 2
Or a neat shortcut is to use the fact that && and || will return the first "truthy" value found - it does not have to be a boolean:
var good = (data && data["good"]) || false;
The jquery tutorial on Operators has a good explanation of what is returned from the && and || operators.
// has a value - sets to "foo"
var data1 = { good: 'foo' };
var good1 = (data1 && data1["good"]) || false;
document.write("<div>" + good1 + "</div>");
// data is null - sets to false
var data2 = null;
var good2 = (data2 && data2["good"]) || false;
document.write("<div>" + good2 + "</div>");
// data["good"] doesn't exist - sets to false
var data3 = { bad: 'hello' };
var good3 = (data3 && data3["good"]) || false;
document.write("<div>" + good3 + "</div>");
Falsy Gotchas!
The other thing to be careful of is that some values you would want to store in your variable may evaluate to false, so you might incorrectly end up with good = false. This depends on what you expect to be inside data["good"].
This will occur for the following, again from the jquery site:
false - boolean false value
"" - Empty strings
NaN - "not-a-number"
null - null values
undefined - undefined values (i.e. if data doesn't have property "good")
0 - the number zero.
If you think this could be the case, you may need to be more specific in your checks:
var data = { good: 0 };
// 0 is falsy, so incorrectly sets to false
var good = (data && data["good"]) || false;
document.write("<div>" + good + "</div>");
// check the actual type and value - correctly set to 0
var good2 = (data && (typeof data["good"] != "undefined") && data["good"] != null)
? data["good"] : false;
document.write("<div>" + good2 + "</div>");
In your case, I can't imagine one-liner, which will check if data is not null and if so, put data[good] into variable.
You must first of all, get rid of Exception.
I would rather do:
var good = false;
if(data){
good = data["good"] || false;
}

eBay Finding API - Why are findItemsAdvanced JSON result elements all arrays?

When calling findItemsAdvanced with RESPONSE-DATA-FORMAT=XML, the results are as expected, e.g:
<findItemsAdvancedResponse xmlns="http://www.ebay.com/marketplace/search/v1/services">
<ack>Success</ack>
<version>1.13.0</version>
<timestamp>2014-11-16T20:59:57.588Z</timestamp>
<searchResult count="0"/>
<paginationOutput>
<pageNumber>0</pageNumber>
<entriesPerPage>100</entriesPerPage>
<totalPages>0</totalPages>
<totalEntries>0</totalEntries>
</paginationOutput>
<itemSearchURL>http://www.ebay.co.uk/sch/i.html?_nkw=mytest1</itemSearchURL>
</findItemsAdvancedResponse>
But calling the same with RESPONSE-DATA-FORMAT=JSON, individual elements are all wrapped in []:
{"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
}
This seems to make it a pain to extract results using Javascript e.g:
response.findItemsAdvancedResponse[0].paginationOutput[0].pageNumber[0]
Am I doing missing something here or doing something wrong? (If not will consider requesting the results in XML and using an XML=>JSON conversion tool...)
Seems no-one else is bothered by this?
I'm using AngularJS and I want to have the Ebay API as a backend. I have to write a $resource interceptor to collapse all arrays that only have 1 child to remove the redundant [] brackets.
A generic interceptor could solve this elegantly however I do think this is a bug.
I've asked the question on the Ebay Developer Form here: https://forums.developer.ebay.com/questions/16385/why-does-search-return-json-items-as-array.html#answer-16386
I have referenced this page on the Ebay forum - hope that helps others.
Edit:
... and for completeness, here is the code I used. It might not be pretty, but it worked for me. YMMV
var resp = {"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
};
var flatten = function( obj ) {
var ret = {};
if( String === obj.constructor || Number === obj.constructor ) {
return obj;
}
for(var prop in obj) {
if(!obj.hasOwnProperty(prop)) continue;
if( String === obj[prop].constructor || Number === obj[prop].constructor ) {
ret[prop] = obj[prop];
continue;
}
if( Object.prototype.toString.call( obj[prop] ) === '[object Array]' ) {
if( obj[prop].length==0 )
ret[prop] = null;
if( obj[prop].length==1 && "0" in obj[prop] ) {
ret[prop] = flatten(obj[prop][0]);
} else {
ret[prop] = flatten(obj[prop]);
}
continue; // skip below: all arrays are Objects...!
}
if( Object === obj[prop].constructor ) {
ret[prop] = flatten( obj[prop] );
continue;
}
}
return ret;
};
console.log( resp );
resp = flatten( resp );
console.log( resp );
console.log( resp.findItemsAdvancedResponse.ack );
This is very similar to DerekC's parse function just quite a bit faster. It is geared towards simply finding elements that are arrays with Array.isArray() and a length of 1 with values that are not objects. Nothing fancy, super clean and super fast.
The function is called ebayFormat, just below the same response object that DerekC used.
var resp = {"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
};
function ebayFormat(item) {
if (typeof item === 'object') {
if (Array.isArray(item) && item.length === 1 && typeof item[0] !== 'object') item = item[0];
else {
var keys = Object.keys(item),
i = 0,
len = keys.length;
for (; i < len; i++) {
if (typeof item[keys[i]] === 'object') item[keys[i]] = ebayFormat(item[keys[i]]);
}
}
}
return item;
}
console.log( resp );
resp = ebayFormat( resp );
console.log( resp );
console.log( resp.findItemsAdvancedResponse.ack );
I got the same problem, I made a recursive function to solve it (remove array when array length=1 and is not exclude)
objJsonFromEbay = findAndCorrect(objJsonFromEbay);
function findAndCorrect(obj){
let o = obj,
exclu = ['item'];
for (const key in o) {
if (o.hasOwnProperty(key)) {
const val = o[key];
if(Array.isArray(val) && val.length == 1){
if(exclu.indexOf(key) == -1)
o[key] = val[0];
o[key] = findAndCorrect(o[key]);
}else if(!Array.isArray(val) && typeof val == 'object'){
o[key] = findAndCorrect(val);
}
}
}
return o;
}
exclu is a Array for each element that you expect keep in a Array format.
Could be useful if ever you use it to get products or another data that you expect in a Array
I had the same issue, and was curious if I'd still have issues if I chose to use an XML response versus a JSON response, and use an XML-to-JSON converter on the returned data -- lo and behold, still the exact same issue.
The converter I would end up deciding to use is called xml2js, which seems to be extremely popular on NPM (north of 3.5M monthly downloads according to NPM at the time of this answer).
xml2js has an option called explicitArray within its parseString behavior, which behaves as they document:
Always put child nodes in an array if true; otherwise an array is created only if there is more than one.
This behavior seems to emulate some of the answers listed here, but because it's a part of a widely-used community solution, I feel more comfortable using it versus a home-grown solution.
In practice, this looks as simple as:
import { parseString as xml2js } from 'xml2js';
...
xml2js(someXmlString, { explicitArray: false }, (err, results) => {
// do something with `results`
});
https://www.npmjs.com/package/xml2js
This is JSON. What did you expect JSON to look like? :-)
{"findItemsAdvancedResponse":[
{"ack":["Success"],
"version":["1.13.0"],
"timestamp":["2014-11-16T20:58:14.639Z"],
"searchResult":[
{"#count":"0"}],
"paginationOutput":[
{"pageNumber":["0"],
"entriesPerPage":["100"],
"totalPages":["0"],
"totalEntries":["0"]}],
"itemSearchURL":["http:\/\/www.ebay.co.uk\/sch\/i.html?&_nkw=mytest1"]
}]
}
Try going to http://jsonviewer.stack.hu/ and pasting the JSON string into the "Text"-section and clicking the "Viewer" tab for a visual representation of the JSON data.
You may want to visit the Wikipedia article on JSON (JavaScript Object Notation):
http://en.wikipedia.org/wiki/JSON#Data_types.2C_syntax_and_example

Common error message in JavaScript

AddPatient = {};
AddPatient.Firstname = FirstNameValue || PatientModel.errorMsg('FirstName',FirstNameValue);
AddPatient.LastName = LastNameValue || PatientModel.errorMsg('LastName',LastNameValue);
AddPatient is an Object and i am checking it whether its blank or not before sending the request.
PatientModel.js
errorMsg: function(title,FirstNameValue,LastNameValue) {
if(FirstNameValue === undefined || FirstNameValue === ' ' && LastNameValue === undefined || LastNameValue = ' ') {
alert('FirstName and LastName are missing');
return false;
} else {
alert(+title 'is missing');
return false;
}
}
I have a form, where i have FirstName and LastName field and i have to check it should not be blank. I want a single function in javascript which can work.
Is this the right way to do it?
I can see a couple of problems in your code.
Mismatch between errorMsg()'s expected arguments and how it is called
Syntax error in second alert()
Bad expression inside if statement
Mismatch between errorMsg()'s expected arguments and how it is called
Your errorMsg() function expects three arguments, but you only pass it two at a time:
errorMsg: function(title,FirstNameValue,LastNameValue) {
....
}
... and then ....
.errorMsg('FirstName',FirstNameValue);
.errorMsg('FirstName',LastNameValue);
If you really want to use both values inside errorMsg(), you need to pass them both every time, in the same order the function expects them:
PatientModel.errorMsg('FirstName',FirstNameValue,LastNameValue);
PatientModel.errorMsg('LastName',FirstNameValue,LastNameValue);
// This is weird code, but it'll work
Syntax error in second alert()
This is simple enough to fix, and could have been just a typo.
alert(+title 'is missing');
^ ^_There's something missing here.
|_This will only try to convert title to a number
What you want is this:
alert(title + 'is missing');
Bad expression inside if statement
if(FirstNameValue === undefined || FirstNameValue === ' ' && LastNameValue === undefined || LastNameValue = ' ') {
This won't work as you expect, because && has greater precedence than ||, meaning the expression will be evaluated as such:
if (
FirstNameValue === undefined
|| (FirstNameValue === ' ' && LastNameValue === undefined)
|| LastNameValue = ' '
) {
You would need parenthesis to fix the precedence:
if( (FirstNameValue === undefined || FirstNameValue === ' ') && (LastNameValue === undefined || LastNameValue = ' ') ) {
This is irrelevant, actually, because the expression can be simplified like this:
// If these values are taken from a form input, they are guaranteed to be strings.
if(FirstNameValue.length === 0 && LastNameValue.length === 0) {
Or even better, like this:
// Uses regular expressions to checks if string is not whitespace only
var WHITESPACE = /^\s*$/;
if( WHITESPACE.test(FirstNameValue) && WHITESPACE.test(FirstNameValue)){
How I would fix your code
This would be an incomplete answer if I didn't provide a correct version of your code, so here it goes. Notice that I separate filling-in of information and its validation in two steps.
PatientModel.js :
validate: function(patient){
var WHITESPACE = /^\s*$/;
var errors = [];
if( WHITESPACE.test(patient.FirstName) ){
// No first name
if( WHITESPACE.test(patient.LastName) ){
// No last name either
errors.push('FirstName and LastName are missing');
}
else {
// Only first name missing
errors.push('FirstName is missing');
}
}
else if( WHITESPACE.test( patient.LastName) ){
// Only last name missing
errors.push('LastName is missing');
}
// Returns array of errors
return errors;
}
Your other code:
AddPatient = {};
AddPatient.Firstname = FirstNameValue;
AddPatient.LastName = LastNameValue;
errors = PatientModel.validate(AddPatient);
if( errors.length != 0 ){
alert('You have the following errors:\n' + errors.join('\n'));
}
Edit: a different, perhaps better, approach. The only difference is we now write validate() as a method of a Patient object:
>>> PatientModel.js:
var WHITESPACE = /^\s*$/;
// Creates a new empty Patient
function Patient(){
this.FirstName = '';
this.LastName = '';
}
// Adds a validate() method to all Patient instances
Patient.prototype.validate: function(){
var errors = [];
if( WHITESPACE.test(this.FirstName) ){
// No first name
if( WHITESPACE.test(this.LastName) ){
// No last name either
errors.push('FirstName and LastName are missing');
}
else {
// Only first name missing
errors.push('FirstName is missing');
}
}
else if( WHITESPACE.test( thisLastName) ){
// Only last name missing
errors.push('LastName is missing');
}
// Returns array of errors
return errors;
}
>>> Your other code :
patient = new Patient();
patient.FirstName = FirstNameValue;
patient.LastName = LastNameValue;
errors = patient.validate();
if( errors.length != 0 ){
alert('You have the following errors:\n' + errors.join('\n'));
}
I would recommend using the Jquery validate plugin for this functionality. It will let you do a hole range of clientside validation. Including required validation.
If you just want pure javascript then your method is fine. I'd make it a more generic validate method which would check each validation rule you have and if it fails add the message to an array. After all your checks are finished if the count of the array is grater then zero then output complete list of errors.

Categories