0

I have an ugly 1MB+ JSON object with numerous deep properties including nested arrays containing nested objects etc.

I'm looking for a function that can return an array of string "paths" for every property in a given object.

['obj.propA.first', 'obj.propA.second', 'obj.propB']

All of my searching thus far has turned up solutions going the other direction. Eg: Taking path strings and fetching the property values.

My gut says there has to be a better way than reinventing the wheel.

Thanks in advance!

Example behavior:

var ugly = {
  a: 'a',
  b: ['b1', 'b2'],
  c: {
    c1: 'c1',
    c2: ['c2a', 'c2b'],
    c3: {
      c3a: 'c3a',
      c3b: [['c3b']],
    },
    c4: [{c4a: 'c4a'}],
  }
};

getPaths = function(obj) {
      ???
};    

getPaths(ugly) = [
      'a',
      'b[0]',
      'b[1]',
      'c.c1',
      'c.c2[0]',
      'c.c2[1]',
      'c.c3.c3a',
      'c.c3.c3b[0][0]',
      'c.c4[0].c4a',
    ];
Peter Hanneman
  • 523
  • 1
  • 5
  • 18
  • 1
    Why do you want these paths? This seems like an odd need (not that it's necessarily wrong, just needs some context) – Nick Tomlin Oct 30 '15 at 22:07
  • `for..in`, recurse on `instanceof Object` (or do some more specific type checking for arrays/etc) – Paul S. Oct 30 '15 at 22:10
  • @NickTomlin - I have a database containing meta data about some of the properties indexed using the path. – Peter Hanneman Oct 31 '15 at 17:45
  • @PaulS. - I'm just really surprised that no one has made a lodash or underscore mixin for this. – Peter Hanneman Oct 31 '15 at 17:46
  • @PeterHanneman not to discount your use case, but I think no one has made this because it is a very rare scenario. I'm relatively young to the industry but i've never encountered or heard a storage/use-case like this. How do your resolve duplicate paths? Do you just want the "terminal" property of each object? I think providing an example source and a result would be helpful. – Nick Tomlin Oct 31 '15 at 18:47
  • I guess you could utilize [Fastest way to flatten / un-flatten nested JSON objects](http://stackoverflow.com/q/19098797/218196) – Felix Kling Oct 31 '15 at 20:15
  • @FelixKling - Nick Tomlin's answer is performing ~22% faster on average for me than the JSON.flatten() – Peter Hanneman Oct 31 '15 at 21:08

1 Answers1

3

This situation is a bit odd, and probably a hint that there are problems at the architectural level, but understand that those things are sometimes inherited and/or unavoidable.

A very bad mutable recursive implementation in node 4.2's version of es2015:

function isPlainishObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]';
}

function propinator (obj, _paths, _currentPath) {
  _paths = _paths || [];

  if (typeof obj !== 'object') {
    _paths.push(_currentPath);
  } else {
    for (let prop in obj) {
      let path;
      if (isPlainishObject(obj)) {
        path = _currentPath && `${_currentPath}.${prop}` || prop;
      } else {
        path = _currentPath && `${_currentPath}[${prop}]` || prop;
      }

      propinator(
        obj[prop],
        _paths,
        path
      );
    }
  }

  return _paths;
}

Tests:

let assert = require('assert');
assert.deepEqual(propinator(ugly), [
  'a',
  'b[0]',
  'b[1]',
  'c.c1',
  'c.c2[0]',
  'c.c2[1]',
  'c.c3.c3a',
  'c.c3.c3b[0][0]',
  'c.c4[0].c4a',
]);    

jsbin

This is only lightly tested (and probably poorly though out) so opinions/improvements are very much welcome.

Nick Tomlin
  • 28,402
  • 11
  • 61
  • 90
  • Node v5 on my i7 Haswell 2.3Ghz was able to do 385KB with 8704 paths in 9.792ms. On a 6.5MB file with 90756 paths it took on average 115.749ms. A bit slow but luckily I have a chance to preprocess and cache. Thanks again. – Peter Hanneman Oct 31 '15 at 20:44
  • It certainly is a strange use case but the data sources that yield the json files don't have access to the meta data. Alas I'm stuck. – Peter Hanneman Oct 31 '15 at 20:53
  • @PeterHanneman gotcha, I understand. Let me know if you come up with any performance tweaks. Glad it worked ;) – Nick Tomlin Oct 31 '15 at 20:55
  • FYI this solution is ~22% faster than the JSON.flatten solution posted by Felix King. – Peter Hanneman Oct 31 '15 at 21:07