Как сделать контрольную сумму из объекта JavaScript?

Мне нужно сделать контрольную сумму из объекта JavaScript.
К сожалению, кажется, что не существует простого способа сделать это из-за упорядочения объектов в JavaScript. Например, возьмите эти объекты:

var obj1 = {type:"cake",quantity:0}
  , obj2 = {quantity:0,type:"cake"};

Я считаю эти объекты равными в данных и хотел бы, чтобы их контрольные суммы были одинаковыми. Я действительно не забочусь о порядке Объекта, пока данные в них одинаковы.
Увы, JSON.stringify оба они на самом деле не равны; как единственный способ сделать контрольную сумму объекта через его представление String, а JSON.stringifyЕ-представления не равны, мои контрольные суммы не будут равны!
Одно решение, которое я придумала, - это воссоздание объекта на основе предопределенной схемы, например, так:

var schema = ["type","quantity"];
function sortify(obj,schema){
  var n={};
  for(i in schema)
    n[schema[i]]=obj[schema[i]];
  return n
}

Бег JSON.stringify(sortify(obj1,schema))==JSON.stringify(sortify(obj2,schema)) вернусь true... но ценой создания нового объекта и перетасовки данных.

Мое другое решение состоит в том, чтобы заменить JSON.stringify метод с одним, который выбирает ключи из предопределенной схемы и систематизирует их значения, а затем соединяет их вместе. Функция читает:

function smarterStringify(obj,schema){
  var s="";
  for(i in schema)
    s+=JSON.stringify(obj[schema[i]]);
  return s
}

Игнорирование того факта, что этот метод не возвращает правильный JSON (достаточно близко к примеру того, что я пытаюсь сделать), это значительное улучшение по сравнению с первым по скорости (по крайней мере, в моем браузере Chrome OS вы можете проверить это сами здесь: http://jsperf.com/sort-then-json-stringify-vs-smarter-stringify) и, конечно, это делает два представления Object String равными!

Однако мне было просто интересно, пропустил ли я что-то, и был ли встроенный метод для чего-то подобного, который не а) не приводил GC JavaScript в патологический случай или б) не выполнял слишком много конкатенаций строк. Я бы предпочел не делать этого.

3 ответа

Решение

Вы можете собрать ключи в массив с Object.keys()отсортируйте этот массив и затем проверьте контрольную сумму ключей / значений в этом известном, предсказуемом порядке. Я не знаю, как использовать JSON.stringify() со всеми отсортированными ключами одновременно, так что вам придется делать свою собственную контрольную сумму.

Я не знаю ни одного встроенного метода для чего-то подобного. Ключи объектов НЕ гарантированно находятся в каком-либо определенном порядке, поэтому полагаться на это небезопасно.


Если у вас нет вложенных объектов или массивов в качестве значений свойств, то вы можете сделать что-то вроде этого:

// creates an array of alternating property name, property value
// with properties in sorted order
// then stringify's that array
function stringifyPropsInOrder(obj) {
    var keys = Object.keys(obj).sort();
    var output = [], prop;
    for (var i = 0; i < keys.length; i++) {
        prop = keys[i];
        output.push(prop);
        output.push(obj[prop]);
    }
    return JSON.stringify(output);
}

function compareObjects(a, b) {
    return stringifyPropsInOrder(a) === stringifyPropsInOrder(b);
}

Если вам нужна более высокая производительность, вам не нужно разбивать на строки (это было сделано здесь для сохранения кода). Вы можете просто вернуть плоский output массив и сравнить массивы напрямую.


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

3 года спустя...

Я столкнулся с этим вопросом, так как хотел хэшировать свои объекты JSON для создания Etag s для моих ответов HTTP. В итоге я написал собственное решение для Node, jsum, который сводится к простому сериализатору:

**
 * Stringifies a JSON object (not any randon JS object).
 *
 * It should be noted that JS objects can have members of
 * specific type (e.g. function), that are not supported
 * by JSON.
 *
 * @param {Object} obj JSON object
 * @returns {String} stringified JSON object.
 */
function serialize (obj) {
  if (Array.isArray(obj)) {
    return JSON.stringify(obj.map(i => serialize(i)))
  } else if (typeof obj === 'object' && obj !== null) {
    return Object.keys(obj)
      .sort()
      .map(k => `${k}:${serialize(obj[k])}`)
      .join('|')
  }

  return obj
}

Затем вы можете взять результат и хешировать его, используя общие алгоритмы (например, SHA256) или используйте удобный метод digest от jsum пакет.


Пожалуйста, обратите внимание на лицензию здесь!

Вы также можете сделать функцию, которая сравнивает ваши объекты на месте:

function compareObjects(a, b) {
  var akeys = Object.keys(a);
  var bkeys = Object.keys(b);
  var len = akeys.length;
  if (len != bkeys.length) return false;
  for (var i = 0; i < len; i++) {
    if (a[akeys[i]] !== b[akeys[i]]) return false;
  }
  return true;
}

Это предполагает, что они являются объектами, переданными, и что они являются простыми плоскими объектами. Можно добавить логику, чтобы проверить эти предположения и рекурсивно проверить, равны ли подобъекты.

Другие вопросы по тегам