Lodash: создание одного объекта из множества объектов слияния/переопределения

Примечание. Я задал этот вопрос под lodash, так как я уверен, что он может помочь мне решить эту проблему красиво, но пока не надавил на нее свой палец.

У меня есть объект, описывающий разные роли пользователя и их разрешения;

У меня будет что-то вроде 10-15 ролей, определенных "как это" (это не отражает код приложения, но сама проблема):

    var role1 = {
    views: {
        v1: {access: true},
        v2: {access: false},
        v#: {access: false}
    }
}

var role2 = {
    views: {
        v1: {access: false},
        v2: {access: true},
        v3: {access: true},
    }
}

Пользователь, подключенный к сети, будет иметь несколько ролей; В этом примере это может быть ['role1', 'role2'], и из этого мне нужно построить единственный объект permissions, который будет представлять собой комбинацию всех реквизитов, определенных во всех ролях пользователя.

В основном это "белый список", где все "истинные" свойства должны переопределять все, что было определено как ложное. Таким образом, ожидаемый результат должен быть:

permissions = {
    views: {
        v1: {access: true},
        v2: {access: true},
        v2: {access: true}
    }
}

Я не слишком уверен, как справиться с этим, не полагаясь на сумасшедшие вложенные циклы

Здесь начальная точка в JSBin: http://jsbin.com/usaQejOJ/1/edit?js,console

Спасибо за вашу помощь!

Ответы

Ответ 1

В Lodash есть несколько методов, которые помогут элегантно решить эту проблему.

Прежде всего, merge. Он принимает несколько исходных объектов и рекурсивно объединяет их свойства в объект назначения. Если два объекта имеют одинаковое имя свойства, последнее значение будет переопределять первое.

Это почти то, что мы хотим; он объединит ваши объекты роли в единый объект. То, что мы не хотим, - это переопределение поведения; мы хотим, чтобы значения true всегда переопределяли теги false. К счастью, вы можете передать пользовательскую функцию слияния, и lodash merge будет использовать это для вычисления объединенного значения, когда два объекта имеют один и тот же ключ.

Мы будем писать пользовательскую функцию для логического OR ваших элементов вместе (вместо того, чтобы позволить более поздним false значениям переопределять true s.), так что если любое значение true, итоговое объединенное значение будет true.

Поскольку наши объекты вложены, нам нужно убедиться, что наша пользовательская функция слияния делает это только OR при сравнении логических значений. При сравнении объектов мы хотим выполнить нормальное слияние их свойств (снова используя нашу пользовательскую функцию для слияния). Это выглядит примерно так:

function do_merge(roles) {

  // Custom merge function ORs together non-object values, recursively
  // calls itself on Objects.
  var merger = function (a, b) {
    if (_.isObject(a)) {
      return _.merge({}, a, b, merger);
    } else {
      return a || b;
    }
  };

  // Allow roles to be passed to _.merge as an array of arbitrary length
  var args = _.flatten([{}, roles, merger]);
  return _.merge.apply(_, args);
}

do_merge([role1, role2, role3]);

Lodash помогает другим способом: вы можете заметить из документов, что _.merge не принимает массив объектов для слияния; вы должны передать их в качестве аргументов. В нашем случае массив был бы ужасно удобным.

Чтобы обойти это, мы будем использовать метод JavaScript apply, который позволяет вам вызывать метод, передавая его аргументы в виде массива, а Lodash - flatten, который принимает массив, который может содержать вложенные массивы - например, [1, [2, 3], [4, 5]] - и выравнивает его на [1, 2, 3, 4, 5].

Итак, мы соберем все необходимые нам аргументы в массиве flatten ed, а затем apply до merge!

Если ваши объекты вложены очень глубоко, есть вероятность, что вы переполните стек, вызывающий merger рекурсивно, как это, но для ваших объектов это должно работать нормально.