#javascript #arrays #object #insert #lodash
#javascript #массивы #объект #вставить #Lodash
Вопрос:
Допустим, у меня есть следующие данные
var obj = {
test: 'somedata',
scores: [
{
"points":99,
"id":"x12"
},
{
"points":21,
"id":"x13"
}
],
sites: [
{
"exercises":21,
"sid":"s12"
},
{
"exercises":23,
"sid":"s11"
}
],
history: {
key: 'value',
commits: [
{
id: 1,
value: 'thank you'
}
]
}
}
Обратите внимание, что scores
и sites
содержат массивы с уникальными элементами на основе id
in scores
и на основе sid
in sites
. Я хочу функцию, которая выполняет следующую магию:
//will **update** obj.test to 'newdata' and return {test:'newdata'}
magicUpdate(obj, {test:'newdata'})
//will **insert** obj.newkey with with value 'value' and return {newkey: 'value'}
magicUpdate(obj, {newkey: 'value'})
//will do nothing and return {}
magicUpdate(obj, {scores: []})
//will **update** scores[0] and return {scores:[{points:3, id: "x12"}]}, as id "x12" is already in the array at index 0
magicUpdate(obj, {scores:[{points:3, id: "x12"}])
//will **insert** {points:3, id: "x14"} into obj.scores and return {scores:[{points:3, id: "x14"}]}
magicUpdate(obj, {scores:[{points:3, id: "x14"}]})
//will **update** sites[0] and return {sites:[{exercises:22, sid: "s12"}]}, as id "s12" is already in the array at index 0
magicUpdate(obj, {sites:[{exercises:22, sid: "s12"}])
//will **insert** {exercises:10, sid: "s14"} into obj.sites and return {sites:[{exercises:10, sid: "s14"}]}
magicUpdate(obj, {sites:[{exercises:10, sid: "s14"}]})
//and also be recursive ...
//will **update** obj.history.commits[0]
magicUpdate(obj, {'history.commits': [{id:1, value: 'changed'}]});
Я видел .update, выполняющий рекурсию, но только в том случае, если кто-то передает path
, который должен определяться автоматически. Затем есть .merge, который внутренне использует _.baseMerge и очень близок к тому, что мне нужно, хотя я не понимаю сигнатуру функции.
_.merge(
{scores:[{id: 12, points:10}, {id: 13, points:10}]},
{scores:[{id: 14, points:10}, {id: 15, points:10}]}
)
// returns {scores:[{id: 14, points:10}, {id: 15, points:10}]} not the fully merged array
Может кто-нибудь указать мне хорошее направление или добиться аналогичных результатов с помощью Lodash?
Комментарии:
1. И что вы пробовали до сих пор?
2. Я попытался написать свою собственную функцию, которая закончилась неудачно, когда я заметил, что мне также нужна рекурсия. Затем я нашел функции Lodash выше и подумал, что у кого-то, возможно, была такая же проблема раньше.
3. Я заметил, что слияние отлично работает для объектов и вложенных объектов. Может быть, я просто пытаюсь преобразовать свою структуру данных из
scores : [{id: s1, points:2},...]
вscores:{s1:{points:2},...}
Ответ №1:
magicUpdate
Функция, которую вы упоминаете в своем сообщении, действительно может быть достигнута с использованием функций Lodash.
Для этой реализации я использовал в основном _ .get , _ .set и _ .UnionWith, хотя я уверен, что это могло быть достигнуто с помощью некоторых других:
// src will be mutated. For simplicity's sake, obj is an object with only one property that represents the changes to make to src
function magicUpdate(src, obj) {
var key = _.first(_.keys(obj)),
value = _.get(obj, key),
srcValue = _.get(src, key),
comparator = function(a, b) {
var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
return a[idKey] === b[idKey];
}
if (_.isArray(srcValue)) {
value = _.unionWith(value, srcValue, comparator);
}
return _.set(src, key, value);
}
Как вы, возможно, заметили, просматривая код, возвращаемый тип — это измененный объект, а не то, что вы спрашиваете. Я не был уверен, что вы хотите в качестве возвращаемого значения.
В любом случае, в Lodash нет встроенной функции разности объектов, поэтому было бы необходимо разработать что-то подобное на случай, если вам нужна разница между старым объектом и измененным (вам также нужно _ .clone объект сначала, чтобы иметь копию и иметь возможность сравнивать).
Идея функции, которую я представляю, состоит в том, чтобы попытаться получить ключ obj (это ключ, который мы хотим изменить src
) и проверить, существует ли он и является ли он массивом. Если это так, мы просто добавляем два массива, обновляя те, в src
которых есть одинаковые id
obj
. Из-за того, что sites
, scores
и history
пришлось id
, и sid
мне пришлось добавить еще немного логики в компаратор _.unionWith
функции.
Если key
не существует или не является массивом, мы просто устанавливаем его src
.
Здесь у вас есть скрипка на случай, если вы захотите поиграть с ней. Надеюсь, это поможет.
Обновить
Мое первое решение предназначалось для одного свойства, обновляемого одновременно. Однако, похоже, что одновременно можно обновлять более одного.
Одним из быстрых решений может быть перебор объекта с обновлениями и обновление по одному свойству за раз.
function updateProperty(src, obj) {
var key = _.first(_.keys(obj)),
value = _.get(obj, key),
srcValue = _.get(src, key),
comparator = function(a, b) {
var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
return a[idKey] === b[idKey];
}
if (_.isArray(srcValue)) {
value = _.unionWith(value, srcValue, comparator);
}
return _.set(src, key, value);
}
function magicUpdate(obj, src) {
_.forEach(src, function(value, key) {
updateProperty(obj, _.pick(src, key));
});
return obj;
}
Комментарии:
1. Большое вам спасибо за вашу любезную помощь. К сожалению, это не работает, когда кто-то пытается установить более одного значения одновременно (что, я должен признать, не было рассмотрено в моих примерах выше, но теперь есть). Смотрите здесь: https://jsfiddle.net/qyf3hbe1 / . Я пришел к следующему решению: https://jsfiddle.net/r0507fyh /
2. @niklas Спасибо! Я обновил ответ возможным решением проблемы: используя текущий метод, обновляйте по одному свойству за раз. Это может сработать! Надеюсь, это поможет.
3. Большое вам спасибо. Я обнаружил, что производительность (из-за множества циклов вашего решения) в ~ 10 раз медленнее, чем у пользовательской функции, которую я запрограммировал. Смотрите здесь jsfiddle.net/r0507fyh/3
Ответ №2:
Я написал решение, которое является рекурсивным и довольно производительным. Посмотрите на эту скрипку.
function mergeRecursive(obj1, obj2) {
if (obj1.constructor == Array) {
for (var i = 0; i < obj1.length; i ) {
if (obj1[i].id == obj2.id) {
obj1[i] = obj2;
return obj1;
}
}
obj1.push(obj2);
return obj1;
}
for (var p in obj2) {
// Property in destination object set; update its value.
if (obj2[p].constructor == Array) {
obj2[p].forEach(function(arrayElement) {
obj1[p] = MergeRecursive(obj1[p], arrayElement);
});
} else if (obj2[p].constructor == Object) {
obj1[p] = MergeRecursive(obj1[p], obj2[p]);
} else {
obj1[p] = obj2[p];
}
}
return obj1;
}