Seth
Seth

Reputation: 580

Check if JSON keys/nodes exist

I'm using the Google Map API to retrieve city + state/region information from a postal code lookup. The issue is that in some cases a postal code lookup won't retrieve a city name. An example is 92625 (U.S).

var g = new GClientGeocoder();
g.setBaseCountryCode('US');
g.getLocations('92625', function(response){
    if (response) {
        var place = response.Placemark[0];
        var state = place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName;
        var city  = place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.LocalityName;
        GLog.write("City = "+city+" : State/Region = "+state+" : Country = " + g.getBaseCountryCode());
    }
});

In certain cases, as mentioned above, there won't be a city name in the result so there will be an undefined error for city, because the key Locality does not exist. This error prevents the rest of the script from running.

I was able to remedy it by...

    if (place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality != null)
        var city  = place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.LocalityName;
    else
        var city = '';

...but this has me paranoid about a similar error for other keys. Eg: If AdministrativeArea is undefined the above IF statement would also cause an undefined error. So should I be checking to see if every Key/Node exists? Seems to be a messy approach because some of these keys are 5+ levels deep...is there an easier way to go about it, maybe some JQuery method I'm not familiar with?

Upvotes: 15

Views: 38965

Answers (4)

Marc
Marc

Reputation: 14295

I like back2dos' approach but I think it could be improved so as not to fail with ReferenceErrors:

function jPath(obj, a) {
    a = a.split(".");
    var p = obj||{};
    for (var i in a) {
        if (p === null || typeof p[a[i]] === 'undefined') return null;
        p = p[a[i]];
    }
    return p;
}

// Tests
var a = {b:{c:'Hello World!'}, c:null};

console.log(jPath(a, 'b.c'));   // Hello World
console.log(jPath(a, 'b.d'));   // null
console.log(jPath(a, 'e.f.g')); // null
console.log(jPath(a, 'c'));     // null

var z;
console.log(jPath(z, 'c'));     // null

This kind of function is great for validating deep JSON return structures from AJAX services such as freebase or YQL.

Upvotes: 3

back2dos
back2dos

Reputation: 15623

Alternatively, you could make a function, that gives you defaults:

function valueOrDefault(val, def) {
    if (def == undefined) def = "";
    return val == undefined ? def : val;
}

And then use it like this:

var place = response.Placemark[0];
var state = valueOrDefault(place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName);
var city  = valueOrDefault(place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.LocalityName);

Personally, I think it's a little nicer to write, than p00ya's proposal, although it's a little hacky fiddling around in undefined objects ... one could maybe change it to this:

function drill(p, a) {
 a = a.split(".");//add this
 for (i in a) {
  var key = a[i];
  if (p[key] == null)
    return '';
  p = p[key];
 }
 return p;
}
var obj = {foo:{bar:{baz:"quux"}}};
var s = drill(obj, "foo.bar.baz"));//and you can use a simple property chain

Upvotes: 9

Chris B
Chris B

Reputation: 15834

You are looking at only the first result the geocoder gives you:

var place = response.Placemark[0];

getLocations() returns a list of several results. If the first one doesn't have it, one of the next few results almost certainly will.

Upvotes: 0

p00ya
p00ya

Reputation: 3709

You could use a function that "drills" down through all those nesting levels, defaulting to the empty string if it can't get that far.

function drill(p, a) {
 for (i in a) {
  var key = a[i];
  if (p[key] == null)
    return '';
  p = p[key];
 }
 return p;
}
var obj = {foo:{bar:{baz:"quux"}}};
var s = drill(obj, ["foo", "bar", "baz"]));

Upvotes: 3

Related Questions