Convert a nested array to an object

The API I'm talking to returns a registry in a very odd structure of nested arrays. I want to convert this monster into an object, so my application has easy access to all the objects stored in this release.

The API result gives me the following:

[ [ "settings", "autoLogout", "false" ], [ "settings", "autoLogoutMinutes", "60" ], [ "settings", "presets", "true" ], [ "controller", "rs232", "ip", "192.168.1.11" ], [ "controller", "rs232", "name", "NX-22" ], [ "source", "M23836", "slot1", "ip", "192.168.1.30" ] ] 

The last value in each array represents the value of the record; every last one adds the key used to store the value. Due to size limitations, I can't just leave large json-encoded objects there, so this is not a viable workaround.

Now I made a rather dirty and slow solution involving 2 eval (). (I know ... no, no, so I'm looking for a better solution) I guess this can be done faster, but I can't figure out how ...

The snippet below uses angular because my application is based on angular, but I'm open to any quick / clean solution. It would be very welcome the approach of vanilla js or some clever use of something like lodash or underscore.

My dirty and slow solution now

 function DemoCtrl($scope){ $scope.data = [ [ "settings", "autoLogout", "false" ], [ "settings", "autoLogoutMinutes", "60" ], [ "settings", "presets", "true" ], [ "controller", "rs232", "ip", "192.168.1.11" ], [ "controller", "rs232", "name", "NX-22" ], [ "source", "M23836", "slot1", "ip", "192.168.1.30" ] ] $scope.init = function(){ var registry = {}; angular.forEach($scope.data, function(entry){ var keys = ''; entry.forEach(function(value, key, entry){ if( key != entry.length - 1 ){ //not last of array, so must be a key keys += '[\'' + value + '\']'; // check if the object already exists if( !angular.isDefined( eval('registry' + keys) ) ){ eval('registry' + keys + ' = {}'); } }else{ //last one in this entry, must be the value eval('registry' + keys + ' = \'' + value + '\''); } }); }); console.log('registry final'); console.log(registry); $scope.registry = registry; } } 
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> <div ng-app> <div ng-controller="DemoCtrl" ng-init="init()"> <pre>{{ registry | json }}</pre> </div> </div> 
+5
source share
7 answers

A compact solution that avoids calculating the position of a value in an array.

 var array = [ ["settings", "autoLogout", "false"], ["settings", "autoLogoutMinutes", "60"], ["settings", "presets", "true"], ["controller", "rs232", "ip", "192.168.1.11"], ["controller", "rs232", "name", "NX-22"], ["source", "M23836", "slot1", "ip", "192.168.1.30"] ], obj = {}; array.forEach(function (a) { var p = obj, v = a.pop(), k = a.reduce(function (r, b) { p[r] = p[r] || {}; p = p[r]; return b; }); p[k] = v; }); document.write('<pre>' + JSON.stringify(obj, 0, 4) + '</pre>'); 
+2
source

Here is a solution that suits you. Also, please never use eval. There is always a better way in JavaScript.

You can adapt the code below to your use case.

 var data = [ [ "settings", "autoLogout", "false" ], [ "settings", "autoLogoutMinutes", "60" ], [ "settings", "presets", "true" ], [ "controller", "rs232", "ip", "192.168.1.11" ], [ "controller", "rs232", "name", "NX-22" ], [ "source", "M23836", "slot1", "ip", "192.168.1.30" ] ]; var o = {}; data.forEach(function(a) { var keys = a.slice(0, a.length-2); var cur = o; keys.forEach(function(k) { if (cur[k] == null) { cur[k] = {}; } cur = cur[k]; }); cur[a[a.length-2]] = a[a.length-1] }); output.innerHTML = JSON.stringify(o, null, 2); 
 <pre id='output'></pre> 
+5
source

Basically, you just loop over them and create nested objects. You do not need to use eval for this. There are many reasons why you should not use it. Performance, security, debugging ( https://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ )

 var asObject = {} //loop over them data.forEach(function(val) { //create the top level object that matches the key if it doesn't exist if (!asObject.hasOwnProperty(val[0])) { asObject[val[0]] = {}; } //store it var theHolder = asObject[val[0]]; //loop over all the middle elements creating nested object for (var index = 1; index < val.length - 2; index++) { var element = val[index]; if (!theHolder.hasOwnProperty[element]) { theHolder[element] = {}; } theHolder = theHolder[element] } //the last one is the value, so just set it var lastKey = val[val.length - 2]; theHolder[lastKey] = val[val.length - 1]; }); console.log(asObject); 
+1
source
 var someObj = $scope.data.reduce(function(accum, array) { var value = array.pop(); //pulls last item off of array //takes the remaining items and condenses them into 1 string var key = array.reduce(function(acc, str) { return acc + str; }, ''); accum[key] = value; return accum; }, {}); //the empty object in this line is the seed value 

Each auxiliary array receives processing and is passed to the empty seed of the object, which is then assigned to someObj .

0
source

 function DemoCtrl($scope){ $scope.data = [ [ "settings", "autoLogout", "false" ], [ "settings", "autoLogoutMinutes", "60" ], [ "settings", "presets", "true" ], [ "controller", "rs232", "ip", "192.168.1.11" ], [ "controller", "rs232", "name", "NX-22" ], [ "source", "M23836", "slot1", "ip", "192.168.1.30" ] ] $scope.init = function(){ var registry = {}; angular.forEach($scope.data, function(entry) { var len = entry.length, tmp = registry; for (var i = 0; i < len - 1; i++) { key = entry[i]; if (i < len - 2) { if (!tmp[key]) { tmp[key] = { }; } tmp = tmp[key]; } else { tmp[key] = entry[i + 1]; } } }); console.log('registry final'); $scope.registry = registry; } } 
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> <div ng-app> <div ng-controller="DemoCtrl" ng-init="init()"> {{ registry }} </div> </div> 
0
source

Here it is done using recursion:

 $scope.registry = $scope.data.reduce(function register(registry, entry) { var key = entry[0]; if (entry.length === 2) { registry[key] = entry[1]; } else { registry[key] = register(registry[key] || {}, entry.slice(1)); } return registry; }, {}); 
0
source

Here is another option based on @Jared Smith's solution above. In his decision, the keys were combined into string keys on a shallow map. This creates the nested object structure of my other solution.

If you are new to array.reduce (), see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

 var someObj = array.reduce(function(previousVal, currentVal) { //strip off the value to use at the end var value = currentVal.pop(); //create all the nested objects currentVal.reduce(function(acc, str, idx, arr) { if (idx !== arr.length - 1 ) { if (!acc.hasOwnProperty(str)) { acc[str] = {}; } return acc[str]; } else { //the last one in the array is the key for the value acc[str] = value; return; } }, previousVal); return previousVal; }, {}); console.log(someObj); 
0
source

Source: https://habr.com/ru/post/1237171/


All Articles