Эффективно переименовывать / повторно отображать javascript/json объектные ключи в массиве объектов
У меня есть некоторые структурированные данные JSON, как это. Давайте предположим, что это взаимозаменяемо, через JSON.parse()
:
[
{
"title": "pineapple",
"uid": "ab982d34c98f"
},
{
"title": "carrots",
"uid": "6f12e6ba45ec"
}
]
Мне нужно, чтобы это выглядело так, переназначение title
в name
, а также uid
в id
с результатом:
[
{
"name": "pineapple",
"id": "ab982d34c98f"
},
{
"name": "carrots",
"id": "6f12e6ba45ec"
}
]
Самый очевидный способ сделать это так:
str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots", "uid": "6f12e6ba45ec"}]';
var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots", "uid": "6f12e6ba45ec"}]';
var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
$('body').append("<pre>"+JSON.stringify(arr, undefined, 4)+"</pre>");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
... или использовать что-то более сложное (хотя и не более эффективное), как это.
Это все прекрасно, но что если в массиве было 200000 объектов? Это много накладных расходов на обработку.
Есть ли более эффективный способ переназначить имя ключа? Возможно, без зацикливания всего массива объектов? Если ваш метод более эффективен, пожалуйста, предоставьте доказательства / ссылки.
7 ответов
Как я уже упоминал в комментариях, если вы можете сделать определенные предположения о значениях объектов, вы можете использовать регулярное выражение для замены ключей, например:
str = str.replace(/"title":/g, '"name":');
Это не так "чисто", но это может сделать работу быстрее.
Если вам все равно придется анализировать JSON, более структурированным подходом было бы передать функцию reviver вJSON.parse
и вы могли бы избежать дополнительного прохода по массиву. Это, вероятно, зависит от того, как двигатель реализует JSON.parse
хотя (может быть, они сначала анализируют всю строку, а затем делают второй проход с помощью функции reviver, и в этом случае вы не получите никакого преимущества).
var arr = JSON.parse(str, function(prop, value) {
switch(prop) {
case "title":
this.name = value;
return;
case "uid":
this.id = value;
return;
default:
return value;
}
});
Тесты, использующие скрипт Node.js ниже для тестирования 3 раза:
1389822740739: Beginning regex rename test
1389822740761: Regex rename complete
// 22ms, 22ms, 21ms
1389822740762: Beginning parse and remap in for loop test
1389822740831: For loop remap complete
// 69ms, 68ms, 68ms
1389822740831: Beginning reviver function test
1389822740893: Reviver function complete
// 62ms, 61ms, 60ms
Кажется, что регулярное выражение (в данном случае) является наиболее эффективным, но будьте осторожны при попытке проанализировать JSON с регулярными выражениями.
Тестовый скрипт, загружающий 100230 строк образца JSON ОП:
fs = require('fs');
fs.readFile('test.json', 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
console.log(new Date().getTime() + ": Beginning regex rename test");
var str = data.replace(/"title":/g, '"name":');
str = str.replace(/"uid":/g, '"id":');
JSON.parse(str);
console.log(new Date().getTime() + ": Regex rename complete");
console.log(new Date().getTime() + ": Beginning parse and remap in for loop test");
var arr = JSON.parse(data);
for (var i = 0; i < arr.length; i++) {
arr[i].name = arr[i].title;
arr[i].id = arr[i].uid;
delete arr[i].title;
delete arr[i].uid;
}
console.log(new Date().getTime() + ": For loop remap complete");
console.log(new Date().getTime() + ": Beginning reviver function test");
var arr = JSON.parse(data, function (prop, value) {
switch (prop) {
case "title":
this.name = value;
return;
case "uid":
this.id = value;
return;
default:
return value;
}
});
console.log(new Date().getTime() + ": Reviver function complete");
});
Задавал этот вопрос давным-давно, и с тех пор я привык к использованию Array.prototype.map() для выполнения работы, в большей степени для стабильности и чистоты кода, чем для производительности. Хотя он, конечно, не самый производительный, он выглядит великолепно:
var repl = orig.map(function(obj) {
return {
name: obj.title,
id: obj.uid
}
})
Если вам нужна более гибкая (и ES6-совместимая функция), попробуйте:
let replaceKeyInObjectArray = (a, r) => a.map(o =>
Object.keys(o).map((key) => ({ [r[key] || key] : o[key] })
).reduce((a, b) => Object.assign({}, a, b)))
например
const arr = [{ abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }]
const replaceMap = { "abc": "yyj" }
replaceKeyInObjectArray(arr, replaceMap)
/*
[
{
"yyj": 1,
"def": 40,
"xyz": 50
},
{
"yyj": 1,
"def": 40,
"xyz": 50
},
{
"yyj": 1,
"def": 40,
"xyz": 50
}
]
*/
Вот еще один вариант предложения ОП использовать map()
для ясности (не производительности).
var newItems = items.map(item => ({
name: item.title,
id: item.uid
}));
При этом используются функции стрелок ES6 и сокращенные синтаксисы, которые возможны, когда в функцию передается только один параметр и только один оператор в теле функции.
В зависимости от вашей истории с лямбда-выражениями на разных языках эта форма может или не может резонировать с вами.
Будьте осторожны при возврате литерала объекта в синтаксисе ярлыка функции стрелки, как этот. Не забудьте про дополнительные символы вокруг буквального объекта!
Если вы хотите сделать его немного более многоразовым. Может быть, это достойный подход.
function rekey(arr, lookup) {
for (var i = 0; i < arr.length; i++) {
var obj = arr[i];
for (var fromKey in lookup) {
var toKey = lookup[fromKey];
var value = obj[fromKey];
if (value) {
obj[toKey] = value;
delete obj[fromKey];
}
}
}
return arr;
}
var arr = [{ apple: 'bar' }, { apple: 'foo' }];
var converted = rekey(arr, { apple: 'kung' });
console.log(converted);
Вы можете использовать пакет npm с именем node-data-transform.
Ваши данные:
const data = [
{
title: 'pineapple',
uid: 'ab982d34c98f',
},
{
title: 'carrots',
uid: '6f12e6ba45ec',
},
];
Ваше отображение:
const map = {
item: {
name: 'title',
id: 'uid',
},
};
И используйте пакет:
const DataTransform = require("node-json-transform").DataTransform;
const dataTransform = DataTransform(data, map);
const result = dataTransform.transform();
console.log(result);
Результат:
[
{
name: 'pineapple',
id: 'ab982d34c98f'
},
{
name: 'carrots',
id: '6f12e6ba45ec'
}
]
Может быть, это не лучший способ для производительности, но это довольно элегантно.
Используя ES6:
const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
return arr.map(s => {
return Object.keys(s).reduce((prev, next) => {
if(next === oldField) {
prev[newField] = s[next]
} else {
prev[next] = s[next]
}
return prev
}, {})
})
}
Используя ES7:
const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
return arr.map(s => {
return Object.keys(s).reduce((prev, next) => {
return next === oldField
? {...prev, [newField]: s[next]}
: {...prev, [next]: s[next]}
}, {})
})
}
var jsonObj = [/*sample array in question*/ ]
Основываясь на различных тестах, обсуждаемых ниже, самое быстрое решение является родным для:
var arr = [];
for(var i = 0, len = jsonObj .length; i < len; i++) {
arr.push( {"name": jsonObj[i].title, "id" : jsonObj[i].uid});
}
Я думаю, что в качестве альтернативы без использования рамок это будет вариант 2:
var arr = []
jsonObj.forEach(function(item) { arr.push({"name": item.title, "id" : item.uid }); });
Всегда есть спор между использованием навигационных и не-навигационных функций. Если я правильно помню, Лодаш утверждал, что они были быстрее, чем подчеркивание, потому что использовали не собственные функции для ключевых операций.
Однако разные браузеры иногда дают очень разные результаты. Я всегда искал лучший средний.
Для тестов вы можете взглянуть на это:
function replaceElem(value, replace, str) {
while (str.indexOf(value) > -1) {
str = str.replace(value, replace);
}
return str;
}
позвонить из основного
var value = "tittle";
var replace = "name";
replaceElem(value, replace, str);