0

I have an object with many nested elements like this

var out = {
   "parentOne":{
      "subParentOne":{
         "childOne":0,
         "childTwo":0
      },
      "subParentTwo":{
         "otherChildOne":0,
         "otherChileTwo":0
      }
   },
   "parentTwo":{
    [...]
   }
}

There are a total of 269 distinct properties in the object.

And I have an array containing the "path" of the property I need to retrieve like this:

var prop = ["parentOne.subParentOne.childOne", "parentOne.subParentTwo.otherChildTwo"]

so this array can contains up to 269 elements.

To access the value of child key I use the following code:

    for (let p in prop) {
        var result = out
        var keys = prop[p].split(".")
        for (let k in keys) {
            result = result[keys[k]]
        }
        // do stuff with the value
    }

But it's highly inefficient so I am looking for a better solution.

Why this doesn't work?

   var result = out["parentOne.subParentOne.childOne"]

So what is the best way to achieve this?

(please note that I'm building my app on top of electron so I'm just interested in a working solution for google chrome)

edit :

forgot to say that object properties can change. Currently, after launching the app, I send a request to a server, get a sample output and parse the result to get available keys with this method :

var availableKeys = []
parseKey(out, '')

function parseKey(object, root) {
        for (let key in object) {
            switch (typeof object[key]) {
                case 'object':
                    parseKey(object[key], (root === '' ? key : root + '.' + key))
                    break
                case 'number':
                    availableKeys.push((root === '' ? '' : root + '.') + key)
                    break
                default:
                    break
            }
        }
    }
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
felix
  • 9,007
  • 7
  • 41
  • 62
  • Where does your array of dotted paths come from? Why are you choosing to represent rules for accessing nested properties in this way? –  Oct 31 '16 at 10:35
  • @torazaburo thing is I get metrics from several servers every seconds and I let the user choose the metrics he wants to display/monitor among the 269 metrics available. I can't change the object structure I receive from servers (the "out" object here) as its generated by a 3rd party library. This is the best solution I could find but if you know a better way to do this please let me know ! – felix Oct 31 '16 at 11:01

3 Answers3

2

but it's highly inefficient...

No, it isn't. It's a string split followed by a loop accessing a few properties on an object. Other than the overhead of the split (minimal) and the overhead of the loop (minimal), it's as efficient as out.parentOne.subParentOne.childOne.

Why this doesn't work?

var result = out["parentOne.subParentOne.childOne"]

Because that's looking for a property with the name parentOne.subParentOne.childOne, which is a perfectly valid property name for a JavaScript object:

var out = {
  "parentOne.subParentOne.childOne": "here's the value"
};
console.log(out["parentOne.subParentOne.childOne"]);

So what is the best way to achieve this?

The way you showed in the question, and/or the various ways detailed in the answers to this question, which will all fundamentally come down to splitting the string and then descending through the object hierarchy as the code in your question does.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Thanks for the answer, that's what I was looking for – felix Oct 31 '16 at 09:53
  • You left out the option of not going down this path at all. I've never seen a convincing case where a mini-DSL to describe access paths into an object was actually necessary. –  Oct 31 '16 at 10:37
1

This is not a direct answer to your question of how to deal with "property paths" such as a.b.c. The answer referenced by TJ Crowder covers that well. This answer talks about other approaches.

In general, if I want to carry out some processing A, I have two alternatives. One is just to write down the logic for A in my host language. I can associate that program with a "name" in some fashion so I can look it up and execute it when I want. The other alternative is to invent a little language (often called a "DSL") which allows me to express my processing requirement A more concisely in the form A'. Then, I write an interpreter for the DSL to parse A' and actually carry out the processing A.

In your case, you have chosen the second alternative. Your DSL is the dot-delimited property path such as a.b.c, and the parser is the code you have written. There is nothing wrong with this, and there are no real performance problems. But it may not be necessary. Instead, consider expressing your metrics as JS functions:

const myMetrics = {
  metric1: obj => obj.parentOne.subParentOne.childOne,
  ...
}

Now when the user selects "metric1", you can retrieve the desired metric simply by saying myMetrics[metricName](out).

This may seem to be an arcane difference. However, consider the case where a particular metric chosen by the user might correspond to something more complex than a simple nested property reference. Perhaps the metric has an array value with a date field, and you want to choose the most recent value for that metric. In that case, with the DSL (property path) approach, you'll need to extend your DSL to allows something like parentOne.subParentOne[@latest].childOne, just as an example, and extend the interpreter correspondingly. Things could quickly get out of hand--before long you might have re-implemented the equivalent of jq. With the approach of associating each user-choosable metric directly with a JS function, you can write definitions of arbitrary complexity.

  • This is much more elegant but is it ES5 compatible? I thought that the '=>' operator was introduced in ES6 – felix Oct 31 '16 at 11:47
  • Entirely equivalent to `function(obj) { return obj.blah.blah; }`. –  Oct 31 '16 at 11:49
  • and let's say that I don't know the available metrics because it can dynamically change depending on server type ect, how could I dynamically fill the "myMetrics" variable? – felix Oct 31 '16 at 12:05
  • I don't know, how were you planning to fill it in with your approach? –  Oct 31 '16 at 12:16
0

This seems to have been answered but I think there may be a simpler solution. A one-liner solution possibly, using JS Array.prototype.map. From your explanation, it appears you already know the number of levels in the JSON structure which is 3. You could then do this to return the values corresponding to the prop keys:

var values = prop.map(key => {var parts = key.split("."); return out[parts[0]][parts[1]][parts[2]];});

The above will return the values in an array in the same order as the keys in props.

I wrote a library RichFlow which can help with a lot more data processing in JavaScript. Live in-browser testing of the library is available at http://richflow.richboy.me/

It just so happened that this is easily solvable by simple array processing since you already have the function that generates the multi-key strings

Richboy
  • 33
  • 4