将对象属性添加到JSON对象中

14

我有一个以此格式表示的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)。

是否有避免将这些少量对象添加到所有元素中的方法?因为要添加的键值对在所有条目中都是相同的。

5个回答

9
您可以使用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);
<pre id="r"></pre>


1
@JcT,简单的测试表明原始版本更快。 - Grundy

7
你确定要使用这种数据格式吗?你应该考虑采用更像这样的格式:
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)的复杂度。如果你想要更简单的东西,就不要移动/复制数据,以当前形式重用它。


这不是真实数据。我只是将其“键”和“值”更改为一些随机名称。 - Uma Kanth

3

使用 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>


3

你说你是从第0个元素复制相同的值,那么你可以将它存储在一个变量中(比如 new_data),然后遍历 data 数组并将其添加到那里。
这和遍历 data 并插入键值对一样复杂。
类似于这样 -

> 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"}"

1
这仍然是O(n * m)。不管怎样,那是一个很好的想法。 - Uma Kanth

3


编辑: 发现一种显著的性能提升方法,比目前测试过的所有解决方案都快:提取其中一个循环并通过new Function(...)应用。本质上是Object.defineProperties(...)的类似于eval的近似方法。已将其添加到以下性能测试中:

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;
  }

测试了几种方法,结果很有趣。我刚刚编写了性能测试代码,欢迎提出任何改进建议。我还添加了一些额外的实现-字符串替换方法和惰性获取器。

总的来说,在大多数建议中,原始循环表现最佳;除了在Chrome浏览器中测试时由@Chris Anderson-MSFT实现使用Underscore的defaults,它似乎实际上更快(但是在IE浏览器中表现不佳)。否则,懒惰者的表现也很稳定。(*编辑:根据上述内容,使用new Function()实现最终被发现是最快的;对于大型对象/迭代,显著)。


下面是以下代码片段的样本输出(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


太棒了 - 很好的问题,非常有趣!我想更好地理解underscore的.defaults实现是如何被V8优化得这么好的,但我还没有完全掌握。上面大部分的测试也在处理头部时有所不同,我可能也应该将其隔离一下;但无论如何都很有趣。 - JcT
发现了一个有趣且高效的解决方案,使用 new Function(...) 实现,已添加到上面。 - JcT
也许尝试一下https://en.wikipedia.org/wiki/Duff%27s_device会很有趣... ;) - JcT

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