2
\$\begingroup\$

I'm looking for suggestions on how to select properties on the child level or deeper dynamically.

For example, here is the method that I am currently using. The method parameters is the object and either a string or array for the property name. If a string is passed used, it will just will get the property. The array is iterated through in order to select the property value.

getMappedDataProperty: function(obj, map) {
  var m, result, _i, _len;
  if (typeof map === 'string') {
    return obj[map];
  }
  result = obj;
  for (_i = 0, _len = map.length; _i < _len; _i++) {
    m = map[_i];
    result = result[m];
  }
  return result;
}

Even though this works, I keep thinking to myself that there has to be a better way of accomplishing this. Suggestions?

\$\endgroup\$
4
  • \$\begingroup\$ That is a really roundabout way of doing things. \$\endgroup\$
    – konijn
    Commented Jan 24, 2014 at 18:37
  • \$\begingroup\$ Here's how I've done it in the past, however I extended it to convert 'foo.bar[0]' => ['foo', 'bar', '0'] \$\endgroup\$
    – megawac
    Commented Jan 24, 2014 at 19:32
  • \$\begingroup\$ @megawac I like the idea of passing in the string 'foo.bar' then splitting it into the array. Might make better sense for the user. \$\endgroup\$
    – chafnan
    Commented Jan 24, 2014 at 20:14
  • \$\begingroup\$ Looks like you're re-inventing JsonPath. I did that once for MVC-style property objects. See goessner.net/articles/JsonPath \$\endgroup\$
    – sqykly
    Commented Jan 25, 2014 at 10:35

2 Answers 2

2
\$\begingroup\$

From a once over:

  • getMappedDataProperty is a clumsy name, how about getProperty.

  • receiving a string is functionally the same as an array with 1 entry, so you could
    map = (typeof map === 'string') ? [map] : map;

  • There is no need for the underscores for i and len, also, I would rather see length than len

  • the use of the temporary variable m is overkill, you could go straight for
    result = result[map[i]];

I would counter propose:

getProperty: function( o, map ) 
{
  map = (typeof map === 'string') ? [map] : map.slice();
  var result = o;
  while ( map.length ) {
    result = result[ map.shift() ];
  }
  return result;
}

I would love to know the context in which you use this, since I cannot see a good use case for it.

\$\endgroup\$
3
  • \$\begingroup\$ Good point on the name. Didn't think about the while loop working for just one in the array. Why do you have the map.slice() in the example on the third line? \$\endgroup\$
    – chafnan
    Commented Jan 24, 2014 at 20:07
  • \$\begingroup\$ I marked as answered, but would still like to see others opinions about this. \$\endgroup\$
    – chafnan
    Commented Jan 24, 2014 at 20:31
  • \$\begingroup\$ To answer what I am doing: I created a generic filter that will output a flat array of a specific property value of the dataset. The issue is that some of the properties are at the child level and others at a grandchild level. Needed to handle getting for both levels. \$\endgroup\$
    – chafnan
    Commented Jan 24, 2014 at 20:34
2
\$\begingroup\$

I'm going to suggest on top of @konijn's changes that:

  1. You shouldn't clone the map as that's just extra overhead as is using shift. I prefer your earlier attempt of just iterating the keys to be honest.
  2. You should check if result[map[i]] exists before you attempt to access it. So getProperty({}, ['foo', 'bar']) won't attempt to access result['foo'] => undefined['bar']. As I mentioned in a comment on the OP I've previously implemented this method and I handled this case by returning undefined but at the bare minimum you should acknowledge it.
  3. I would also suggest you handle numbers in your implementation.

I would suggest an implementation along the lines of:

getProperty: function(result, map) {
    var type = typeof map, i = 0, len = map.length;
    if (type === 'string' || type === 'number') return result[map];
    for (; i < len; i++) {
      if(map[i] in result) result = result[map[i]];
      else return undefined;
    }
    return result;
}

Later if you decide to handle a map string like 'foo.bar' it would also be easy to extend this implementation (and it'd start to look even more like mine lol). All it would take is adapting the string case to map = map.toString().split('.')

\$\endgroup\$
1
  • \$\begingroup\$ +1, I forgot about the fact that the function is very trusting! \$\endgroup\$
    – konijn
    Commented Jan 24, 2014 at 22:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.