3

So I have a callback function which provides a json object:

function(object){
    var data=object.packet.link.ip.tcp.data;
    console.log(data.toString());
}

The problem I have is that any of packet/link/ip/tcp/data can be "undefined" - my node.js program falls over every time it hits an undefined variable.

I tried putting the declaration of "data" inside try/catch, however I keep getting "undefined" errors - my guess is that I'd need to put object/object.packet/object.packet.link/object.packet.link.ip/etc. in try/catch.

So, I found some elegant coffeescript:

if object?.packet?.link?.ip?.tcp?.data? then doStuff()

which compiles to:

var _ref, _ref1, _ref2, _ref3;

if ((typeof object !== "undefined" && object !== null ? (_ref = object.packet) != null ? (_ref1 = _ref.link) != null ? (_ref2 = _ref1.ip) != null ? (_ref3 = _ref2.tcp) != null ? _ref3.data : void 0 : void 0 : void 0 : void 0 : void 0) != null) {
       //doStuff:
       var data = object.packet.link.ip.tcp.data;
       console.log(data.toString());
}

Yuck!

Now it works perfectly, but I was just wondering if there's a more elegant (readable) way of doing this in pure Javascript?

Eamorr
  • 9,872
  • 34
  • 125
  • 209
  • 1
    the nice thing about the existential operator in coffeescript is that it is contextually aware (because of the compilation step). http://tinyurl.com/nqx2aja but I would consider it a code smell to chain it this deep. I like that reduce solution a lot. – Jed Schneider Nov 13 '14 at 19:39

3 Answers3

5

You can do

["packet", "link", "ip", "tcp", "data"]
  .reduce(function (m, i) { if (m) return m[i]; }, object);

You could move the reduce into a function and have get(object, "packet", "link", "ip", "tcp", "data"). It can be pretty, although a simple && solution might be more sensible.

Karolis Juodelė
  • 3,708
  • 1
  • 19
  • 32
  • At work I made this: `get(object, 'packet.link.ip.tcp.data')`. It's nearly identical to yours, except there's a `.split('.')` call. – Keen Nov 15 '14 at 00:28
  • @Cory, That's great, until you need to use an object `{"packet.link.ip.tcp.data": ...}`. – Karolis Juodelė Nov 15 '14 at 07:11
  • Yes, we don't allow such keys normally. If anything like that comes up, we do have an alternative method that takes an array. Since that's identical to your method, it didn't seem worth mentioning until you pointed out that it is necessary in certain cases. – Keen Nov 15 '14 at 20:42
3

If it is just enough to check whether or not you're dealing with truthy values, you might go like

var data = object && object.packet && object.packet.link && object.packet.link.ip; // etc.

still no beauty, but most likely the best you can go for. Of course, you always have the option of introducing the good ol' try..catch clause.

try {
    var data = object.packet.link.ip.tcp.data;
} catch( ex ) { }

Anything "better" than that in terms of "elegance" (whoever that defines) would require a custom written function, which walks step by step through the object properties, checking for existence.

jAndy
  • 231,737
  • 57
  • 305
  • 359
  • Hi, I tried the try/catch - though I had trouble with the deep nesting and kept getting "undefined" errors... – Eamorr Nov 13 '14 at 19:22
1
function A(object)
{
 var data = object?object.packet?object.packet.link?object.packet.link.ip?object.packet.link.ip.tcp?  object.packet.link.ip.tcp.data:null:null:null:null:null;
 console.log(data);
}
  • Note:

    If data has null, you can't call toString on null, as null(along with undefined) are the only two primitives which have no object wrappers and subsequently no toString() functions.

Aravind
  • 3,169
  • 3
  • 23
  • 37