【问题标题】:Adding object properties into a JSON object将对象属性添加到 JSON 对象中
【发布时间】:2015-09-21 22:40:29
【问题描述】:

我有一个这种格式的 JSON 对象。

[
    {
        "name": "schoolname",
        "line id": "0",
        "time": "4-5",
        "minage": "15",
        "maxage": "35"
    },
    {
        "name": "studentname1",
        "line id": "1",
        "class": "A"
    },
    {
        "name": "studentname2",
        "line id": "2",
        "class": "B"
    }
]

我想做的事

从一组指定的标头中,从"line id" : "0" 中获取,并将其设置为其他项。

例如: headers = ["time", "minage", "maxage"]

我从"line id" : "0" 得到这些,然后像这样送给其他人。

[
    {
        "name": "schoolname",
        "line id": "0",
        "time": "4-5",
        "minage": "15",
        "maxage": "35"
    },
    {
        "name": "studentname1",
        "line id": "1",
        "class": "A",
        "time": "4-5",
        "minage": "15",
        "maxage": "35"
    },
    {
        "name": "studentname2",
        "line id": "2",
        "class": "B",
        "time": "4-5",
        "minage": "15",
        "maxage": "35"
    }
]

然后删除带有"line id" : "0"的元素,像这样:

[
    {
        "name": "studentname1",
        "line id": "1",
        "class": "A",
        "time": "4-5",
        "minage": "15",
        "maxage": "35"
    },
    {
        "name": "studentname2",
        "line id": "2",
        "class": "B",
        "time": "4-5",
        "minage": "15",
        "maxage": "35"
    }
]

据说第一个元素是"line id" : "0"

我的尝试:

var headers = ["time", "minage", "maxage"]
var data = 
    [
        {
            "name": "schoolname",
            "line id": "0",
            "time": "4-5",
            "minage": "15",
            "maxage": "35"
        },
        {
            "name": "studentname1",
            "line id": "1",
            "class": "A"
        },
        {
            "name": "studentname2",
            "line id": "2",
            "class": "B"
        }
    ];

for(var i = 1; i < data.length; i++)//iterate over the data leaving the 1st line
{
    for(var j = 0; j < headers.length; j++)//add each header to the data lines
     {
        data[i][headers[j]] = data[0][headers[j]];
     }
}
data.splice(0,1);

一切正常并按预期工作。有没有办法降低时间复杂度并提高效率。

现在的时间复杂度为 O(n * m)。

有没有办法将这几个对象添加到所有元素中?由于要为所有条目添加的键值对保持不变。

【问题讨论】:

    标签: javascript arrays json


    【解决方案1】:

    你可以像Object.defineProperties一样使用

    var arr = [{
        "name": "schoolname",
        "line id": "0",
        "time": "4-5",
        "minage": "15",
        "maxage": "35"
      }, {
        "name": "studentname1",
        "line id": "1",
        "class": "A"
      }, {
        "name": "studentname2",
        "line id": "2",
        "class": "B"
      }],
      headers = ["time", "minage", "maxage"];
    
    
    function addHeaders(arr, headers) {
      var header = arr.splice(0, 1)[0],
        propObj = headers.reduce(function(acc, el) {
          acc[el] = {
            value: header[el],
            writable: true,
            enumerable: true
          };
          return acc;
        }, {});
    
      for (var i = 0, len = arr.length; i < len; i++) {
        Object.defineProperties(arr[i], propObj);
      }
      return arr;
    }
    
    document.getElementById('r').innerHTML = 'initial: ' + JSON.stringify(arr,null,2) + '<br/>';
    document.getElementById('r').innerHTML += 'result: ' + JSON.stringify(addHeaders(arr, headers),null,2);
    &lt;pre id="r"&gt;&lt;/pre&gt;

    【讨论】:

    • @JcT,天真的测试说原来的更快
    【解决方案2】:

    您已修复该数据格式吗?你应该考虑做一些更像

    school
        -> info (name, etc.)
        -> [classes]
           -> info
           -> [student_ids]
        -> [students]
           -> info (id)
    

    如果您无法更改格式。你可以用Underscore.js#default 做你想做的事情。假设line_id=0 总是data[0]

    var keys = ['minage','maxage','time'];
    var temp = _.pick(data.shift(),keys);
    data.forEach(function(e, i, a) {
        a[i] = _.default(e,temp);
    });
    

    它并没有真正降低您的复杂性,因为您基本上是在寻找一个大小为 N 的数组并更新属性计数 M,这意味着您将拥有 O(N*M) 的复杂性。如果您想要不那么复杂的东西,请不要移动/复制数据。以当前形式重复使用它。

    【讨论】:

    • 这不是真实数据。我只是将它的keyvalues 更改为某个随机名称。
    【解决方案3】:

    既然你说你从第 0 个元素复制相同的值,你可以将它存储在一个变量中(比如 new_data),然后遍历 data 数组并将它们添加到那里。
    这与遍历 data 并插入 key-val 对一样复杂。
    像这样 -

    > new_data = {}
    //Getting all the content with header keys in data into new_data
    > headers.forEach(function(v){new_data[v] = data[0][v]})
    
    //Storing the new_data
    > new_data
    Object {time: "4-5", minage: "15", maxage: "35"}
    
    //Adding the new_data into data
    > data.forEach(function(d_val){
        for(k_nd in new_data){
            d_val[k_nd] = new_data[k_nd];
        }
      });
    
    //Removing the 0th array element
    > data.splice(0, 1)
    
    //Checking it 
    > JSON.stringify(data[0])
    "{"name":"studentname1","line id":"1","class":"A","time":"4-5","minage":"15","maxage":"35"}"
    

    【讨论】:

    • 这仍然是O(n * m)。无论如何,这是个好主意。
    【解决方案4】:

    使用 lodash 库:

    var headers = ["time", "minage", "maxage"];
        var data = [{
          "name": "schoolname",
          "line id": "0",
          "time": "4-5",
          "minage": "15",
          "maxage": "35"
        }, {
          "name": "studentname1",
          "line id": "1",
          "class": "A"
        }, {
          "name": "studentname2",
          "line id": "2",
          "class": "B"
        }];
    
        var temp = _.pick(data[0], headers);
        data.splice(0, 1);
        for (var i = 0; i < data.length; i++) {
          _.merge(data[i], temp);
        }
    var result = JSON.stringify(data);
        
    $('#result').text(result);
    <script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    
    <div id="result"></div>

    【讨论】:

      【解决方案5】:


      编辑: 发现了一个显着的性能提升器,比迄今为止所有其他经过测试的解决方案都快:提取其中一个循环并通过:new Function(...) 应用。本质上是eval 的近似Object.defineProperties(...)。已将此添加到以下性能测试中:
      function addHeadersNewFunc(arr, headers) {
          //console.time('addHeadersNewFunc');
          var header = arr.shift(),
          funcBody = ['return item;'],
          headerPropName,
          setProps;
          for(var h = headers.length; h--;) {
            headerPropName = headers[h];
            funcBody.unshift('item["' + headerPropName + '"]="' + header[headerPropName] + '";'); //unshift since loop is reversed and we want props in same add order as other implementations, and we've already added our first line
          }
          setProps = new Function('item', funcBody.join('')); //warning, this is a form of 'eval()'...
      
          for (var i = arr.length; i--;)
          {
            setProps(arr[i]);
          }
          //console.timeEnd('addHeadersNewFunc');
          return arr;
        }
      

      一些有趣的结果测试了几种不同的方法。我刚刚编写了性能测试代码,对任何推荐的改进感到高兴。我还添加了一些额外的实现 - 字符串替换方法和惰性 getter。

      总的来说,原始循环看起来优于大多数其他建议;除了 @Chris Anderson-MSFT 在 Chrome 中测试时使用 Underscore 的 defaults 实现的,这似乎实际上更快(但是在 IE 中表现不佳)。否则,懒人的表现也一直很好。 (* 编辑: 如上所述,最终发现使用 new Function() 的实现是最快的;对于大型对象/迭代,显着)。


      以下 sn-p (Chrome 43) 的示例输出:

      Items: 2000
      Output of functions is all consistent: true
      Testing...
      
      addHeadersOrig x 1000: [Avg] 2.3977ms, [Min] 2.3170ms, [Max] 2.8280ms
      addHeadersDefineProp x 1000: [Avg] 6.3481ms, [Min] 6.1010ms, [Max] 15.1750ms
      addHeadersStrReplace x 1000: [Avg] 3.0551ms, [Min] 2.6630ms, [Max] 5.9910ms
      addHeadersUnderscoreDefaults x 1000: [Avg] 1.4344ms, [Min] 1.1800ms, [Max] 9.5100ms
      addHeadersLazy x 1000: [Avg] 2.4529ms, [Min] 2.3460ms, [Max] 6.0770ms
      addHeadersLazyMemo x 1000: [Avg] 2.4837ms, [Min] 2.3760ms, [Max] 3.8420ms
      addHeadersNewFunc x 1000: [Avg] 0.0959ms, [Min] 0.0430ms, [Max] 0.5070ms
      

      (function() {
      
        "use strict";
      
        var arr = [{
            "name": "schoolname",
            "line id": "0",
            "time": "4-5",
            "minage": "15",
            "maxage": "35"
          }, {
            "name": "studentname1",
            "line id": "1",
            "class": "A"
          }, {
            "name": "studentname2",
            "line id": "2",
            "class": "B"
          }],
          headers = ["time", "minage", "maxage"];
      
        //add some more...
        for (var i = 3, iLen = 2000; i < iLen; i++) {
          arr.push({
            name: "studentname" + i,
            "line id": String(i),
            "class": "C"
          });
        }
      
        function addHeadersOrig(arr, headers) {
          //console.time('addHeadersOrig');
          for (var i = 1; i < arr.length; i++) //iterate over the data leaving the 1st line
          {
            for (var j = 0; j < headers.length; j++) //add each header to the data lines
            {
              arr[i][headers[j]] = arr[0][headers[j]];
            }
          }
          arr.splice(0, 1);
          //console.timeEnd('addHeadersOrig');
          return arr;
        }
      
        function addHeadersDefineProp(arr, headers) {
          //console.time('addHeadersDefineProp');
          var header = arr.splice(0, 1)[0],
            propObj = headers.reduce(function headerReduce(acc, el) {
              acc[el] = {
                value: header[el],
                writable: true,
                enumerable: true
              };
              return acc;
            }, {});
      
          for (var i = 0, len = arr.length; i < len; i++) {
            Object.defineProperties(arr[i], propObj);
          }
          //console.timeEnd('addHeadersDefineProp');
          return arr;
        }
      
        function addHeadersStrReplace(arr, headers) {
          //console.time('addHeadersStrReplace');
          var header = arr.shift(),
            propObj = {};
          for (var i = 0; i < headers.length; i++) {
            propObj[headers[i]] = header[headers[i]];
          }
          //stringify the array, replace each '}' with a ',' followed by the the stringified propObj (minus its opening bracket) which brings its own closing bracket to make up for the one we replaced; then parse back to an object
          arr = JSON.parse(JSON.stringify(arr).replace(/\}/g, ',' + JSON.stringify(propObj).slice(1)));
          //console.timeEnd('addHeadersStrReplace');
          return arr;
        }
      
        //only runs using lodash, not underscore
        function addHeadersLodashMerge(arr, headers) {
          //console.time('addHeadersLodashMerge');
          var temp = _.pick(arr.shift(), headers);
          for (var i = 0; i < arr.length; i++) {
            _.merge(arr[i], temp);
          }
          //console.timeEnd('addHeadersLodashMerge');
          return arr;
        }
      
        //runs under both lodash and underscore - faster in underscore AFAICT
        function addHeadersUnderscoreDefaults(arr, headers) {
          //console.time('addHeadersUnderscoreDefaults');
          var temp = _.pick(arr.shift(), headers);
          arr.forEach(function(e, i, a) {
            a[i] = _.defaults(e, temp);
          });
          //console.timeEnd('addHeadersUnderscoreDefaults');
          return arr;
        }
        
        function addHeadersNewFunc(arr, headers) {
          //console.time('addHeadersNewFunc');
          var header = arr.shift(),
          funcBody = ['return item;'],
          headerPropName,
          setProps;
          for(var h = headers.length; h--;) {
            headerPropName = headers[h];
            funcBody.unshift('item["' + headerPropName + '"]="' + header[headerPropName] + '";'); //unshift since loop is reversed and we want props in same add order as other implementations, and we've already added our first line
          }
          setProps = new Function('item', funcBody.join('')); //warning, this is a form of 'eval()'...
          
          for (var i = arr.length; i--;)
          {
            setProps(arr[i]);
          }
          //console.timeEnd('addHeadersNewFunc');
          return arr;
        }
      
        function addHeadersLazy(arr, headers) {
          //console.time('addHeadersLazy');
          var lazy = new Lazy(arr, headers),
            result = [];
          for (var i = 1; i < arr.length; i++) {
            result.push(lazy.get(i));
          }
          //console.timeEnd('addHeadersLazy');
          return result;
        }
      
        function addHeadersLazyMemo(arr, headers) {
          //console.time('addHeadersLazyMemo');
          var lazy = new Lazy(arr, headers, true),
            result = [];
          for (var i = 1; i < arr.length; i++) {
            result.push(lazy.get(i));
          }
          //console.timeEnd('addHeadersLazyMemo');
          return result;
        }
      
        function Lazy(arr, headers, useMemo) {
          var headerValSrc = arr[0],
            headerLen = headers.length,
            memo = [];
      
          function _get(index) {
            for (var j = 0; j < headerLen; j++) {
              arr[index][headers[j]] = headerValSrc[headers[j]];
            }
            return arr[index];
          }
      
          function _getMemo(index) {
            if (memo[index]) {
              return memo[index];
            }
      
            for (var j = 0; j < headerLen; j++) {
              arr[index][headers[j]] = headerValSrc[headers[j]];
            }
            return (memo[index] = arr[index]);
          }
      
          return {
            get: (useMemo ? _getMemo : _get)
          };
        }
      
        function clone(data) {
          return JSON.parse(JSON.stringify(data));
        }
      
        function perfTest(name, testFunc) {
          name = name ? name : "Test";
      
          var iterations = 1000,
            argsSliced = Array.prototype.slice.call(arguments, 2),
            args = [],
            t0 = 0,
            t1,
            t2,
            t3,
            tmin = 1000000,
            tmax = 0,
            output;
          setTimeout(function delayAllowingDocWrite() {
            for (var i = 0; i < iterations; i++) {
              args = clone(argsSliced);
              t1 = performance.now();
              testFunc.apply(this, args);
              t2 = performance.now();
              t3 = t2 - t1;
              tmin = t3 < tmin ? t3 : tmin;
              tmax = t3 > tmax ? t3 : tmax;
              t0 += t3;
            }
      
      
            output = name + " x " + iterations + ": [Avg] " + (t0 / iterations).toFixed(4) + "ms, [Min] " + tmin.toFixed(4) + "ms, [Max] " + tmax.toFixed(4) + "ms";
            console.log(output);
            document.body.innerHTML += (output + "<br />");
          }, 10);
      
          return testFunc.apply(this, clone(argsSliced)); //return output of function immed, once, for comparing results
        }
      
        document.body.innerHTML += "Items: " + arr.length + "<br />";
        console.log("Items: ", arr.length);
      
        //*
        var resultOrig = perfTest("addHeadersOrig", addHeadersOrig, arr, headers),
          resultDefineProp = perfTest("addHeadersDefineProp", addHeadersDefineProp, arr, headers),
          resultStrReplace = perfTest("addHeadersStrReplace", addHeadersStrReplace, arr, headers),
          //resultLodashMerge = perfTest("addHeadersLodashMerge", addHeadersLodashMerge, arr, headers), //re-enable if using lodash.min.js
          resultUnderscoreDefaults = perfTest("addHeadersUnderscoreDefaults", addHeadersUnderscoreDefaults, arr, headers),
          resultLazy = perfTest("addHeadersLazy", addHeadersLazy, arr, headers),
          resultLazyMemo = perfTest("addHeadersLazyMemo", addHeadersLazyMemo, arr, headers),
          resultNewFunc = perfTest("addHeadersNewFunc", addHeadersNewFunc, arr, headers);
        //*/
      
        var resultOrigStr = JSON.stringify(resultOrig),
          outputIsConsistent = "Output of functions is all consistent: " + (
            resultOrigStr === JSON.stringify(resultDefineProp) &&
            resultOrigStr === JSON.stringify(resultStrReplace) &&
            //resultOrigStr === JSON.stringify(resultLodashMerge) &&
            resultOrigStr === JSON.stringify(resultUnderscoreDefaults) &&
            resultOrigStr === JSON.stringify(resultLazy) &&
            resultOrigStr === JSON.stringify(resultLazyMemo) &&
            resultOrigStr === JSON.stringify(resultNewFunc)
          );
        document.body.innerHTML += outputIsConsistent + "<br /><em>Testing...</em><br /><br />";
        console.log(outputIsConsistent);
      
        if (!window.performance || !window.performance.now) {
          document.body.innerHTML += "Your browser does not seem to support performance.now()...";
        }
      
        /*
        var arr1 = clone(arr),
          arr2 = clone(arr),
          arr3 = clone(arr),
          arr4 = clone(arr),
          arr5 = clone(arr),
          arr6 = clone(arr);
        var resultOrig = addHeadersOrig(arr1, headers),
          resultDefineProp = addHeadersDefineProp(arr2, headers),
          resultStrReplace = addHeadersStrReplace(arr3, headers),
          resultLodash = addHeadersLodash(arr4, headers),
          resultLazy = addHeadersLazy(arr5, headers),
          resultLazyMemo = addHeadersLazyMemo(arr6, headers);
        console.log(resultOrig);
        console.log(resultDefineProp);
        console.log(resultStrReplace);
        console.log(resultLodash);
        console.log(resultLazy);
        console.log(resultLazyMemo);
        //*/
      
      
      })();
      body {
        font-size: 0.8em;
        font-family: "Arial", sans-serif;
      }
      <!--script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script-->
      <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
      <p>Use a browser that supports performance.now().</p>

      为了更容易玩:Plnkr

      【讨论】:

      • 干杯 - 好问题,非常有趣!我想更好地了解下划线对.defaults 的实现如何被V8 优化得如此之好,但我还没有掌握它。上面的大多数测试在处理标头的方式上也有所不同,我可能也应该将其隔离一点;但无论如何都很有趣。
      • 使用new Function(...)找到了一个有趣且高性能的解决方案,已添加到上述内容中。
      • 试试en.wikipedia.org/wiki/Duff%27s_device 可能会很有趣... ;)
      猜你喜欢
      • 2018-07-27
      • 2015-09-01
      • 2015-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-03
      • 2021-11-12
      • 2021-03-22
      相关资源
      最近更新 更多