将嵌套数组转换为对象

5

我需要翻译的内容是:一个我正在使用的API返回一个非常奇怪的嵌套数组结构作为其注册表。我想将这个可怕的东西转换成一个对象,以便我的应用程序可以轻松地访问存储在这个输出中的整个对象。

API返回给我的输出如下:

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

每个数组的最后一个值代表一个条目的值,它之前的所有值加起来就是用于保存该值的键。由于大小限制,我不能只是将大型json编码对象放在那里,所以这不是可行的解决方法。
我现在使用了两个eval()的非常低效的解决方案。(我知道...那是个大忌,所以我正在寻找更好的解决方案)。我猜这可以更快地完成,但我想不出如何做到...
下面的代码片段使用了angular,因为我的应用程序是基于Angular的,但我也接受任何快速/干净的解决方案。使用原生js方法或巧妙地使用lodash或underscore等工具将非常受欢迎。

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>


可能是 将数组转换为对象 的重复问题。 - Jared Smith
除非你确切知道自己在做什么,否则绝对不要使用 eval - Jared Smith
@dfsq 谢谢你的编辑,我正在寻找那个过滤器 ;) - JasperZelf
Array.pop() 弹出你想要的最后一个值,然后使用 Array.reduce() 将其余部分合并为字符串键。 - Jared Smith
7个回答

4
这里有一个符合你需求的解决方案。同时请不要使用eval函数,JavaScript中总是有更好的方法。你可以将以下代码调整为你自己的用例。

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>


谢谢!我知道eval是一件坏事……这就是为什么我在寻求帮助以找到更好的解决方案。 - JasperZelf

2
一个简洁的解决方案,避免了对数组中值位置的计算。

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


如果将a [0]作为initialValue传递而不是undefined,是否可以避免if(r)条件检查? - AndreaBogazzi
@AndreaBogazzi,由于if语句的存在,会发生这种情况。r不再是未定义的,而且a[0]会被处理两次,一次作为r的起始值,一次作为b的值。 - Nina Scholz
@AndreaBogazzi,感谢您的提问。我更改了回调函数。现在没有起始值,因为没有函数不需要比较,这样甚至更短。它与数组中只有一个项时一样有效,因为该项将被返回。 - Nina Scholz

1
基本上,您只需要循环它们并创建嵌套对象即可。您不需要使用eval。有很多原因不应该使用它。性能,安全性,调试能力(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){ 
 $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
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

对于每个子数组,都会进行处理并传入空对象种子中,然后将其分配给 someObj


0

这里使用递归完成:

$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
这是基于@Jared Smith上面的解决方案的另一个选项。在他的解决方案中,键被连接成浅映射中的字符串键。这样就创建了我其他解决方案中的嵌套对象结构。
如果你对array.reduce()不熟悉,请参阅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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接