2015-12-01 23 views
5

Un'API con cui sto parlando restituisce il suo registro in una struttura di array nidificata molto dispari. Voglio convertire questa mostruosità in un oggetto in modo che la mia applicazione abbia facile accesso a tutti gli oggetti memorizzati all'interno di questo output.Converti matrice nidificata in oggetto

L'uscita l'API mi dà assomiglia a questo:

[ 
    [ "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" ] 
] 

L'ultimo valore in ogni matrice rappresenta il valore di una voce, tutto prima che l'ultimo si aggiunge alla chiave utilizzata per salvare il valore. A causa delle limitazioni di dimensione, non posso semplicemente rilasciare grandi oggetti json-encoded, quindi non è una soluzione praticabile.

Ora ho creato una soluzione piuttosto sporca e lenta che coinvolge 2 eval(). (Lo so ... è un no-no, quindi sto cercando una soluzione migliore) Immagino che questo possa essere fatto molto più velocemente, ma non riesco a capire come ...

Il frammento di seguito usa angolare perché la mia applicazione è basata su Angular, ma sono aperto a qualsiasi soluzione veloce/pulita. Un approccio alla vaniglia o un uso intelligente di qualcosa come lodash o underscore sarebbe molto gradito.

La mia soluzione sporco e lento ora

function DemoCtrl($scope){ 
 
\t $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{ 
 
       \t //last one in this entry, must be the value 
 
        \t eval('registry' + keys + ' = \'' + value + '\''); 
 
       \t } 
 
       
 
      });   
 
     }); 
 
     
 
     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>

+0

Eventuali duplicati di [Convertire Array a Object] (http://stackoverflow.com/questions/4215737/convert-array- to-object) –

+0

Obbligatorio mai * mai * usare 'eval' a meno che tu non sappia * esattamente * cosa stai facendo. –

+0

@dfsq grazie per quella modifica, cercava quel filtro;) – JasperZelf

risposta

2

Una soluzione compatta che evita il calcolo della posizione valore nella matrice.

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>');

+0

passando come valore iniziale a [0] anziché non definito, non si eviterebbe il controllo della condizione if (r)? – AndreaBogazzi

+0

@AndreaBogazzi, lo farebbe, a causa della dichiarazione if. 'r' non è quindi indefinito e' a [0] 'viene elaborato due volte, uno come valore iniziale per' r' e uno come valore per 'b'. –

+0

@AndreaBogazzi, grazie per la domanda. Ho cambiato la richiamata. ora non esiste alcun valore iniziale, perché senza la funzione non è necessario il confronto ed è ancora più breve. funziona bene come con un elemento nell'array, poiché l'oggetto viene quindi restituito. –

5

Ecco una soluzione che si adatta alle vostre necessità. Inoltre, per favore, non usare mai eval. C'è sempre un modo migliore in JavaScript.

È possibile adattare il codice di seguito al proprio caso d'uso.

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>

+0

Grazie! So che la valutazione è una brutta ... brutta cosa, ecco perché sto chiedendo aiuto per trovare una soluzione migliore. – JasperZelf

0
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 

Ogni sub-array ottiene il trattamento e passato attraverso il seme oggetto vuoto che viene poi assegnato someObj.

1

Fondamentalmente è sufficiente passarle sopra e creare oggetti nidificati. Non è necessario utilizzare eval per questo. Ci sono molti motivi per cui non dovresti usarlo. Prestazioni, sicurezza, debuggability (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); 
0

function DemoCtrl($scope){ 
 
\t $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

Qui è fatto utilizzando la ricorsione:

$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

Ecco un'altra opzione sulla base di soluzione di @Jared Smith sopra. Nella sua soluzione le chiavi erano concatenate in chiavi di stringa in una mappa poco profonda. Questo crea la struttura ad oggetti nidificati della mia altra soluzione.

Se siete nuovi a array.reduce(), vedere 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);