1. ホーム
  2. ジャバスクリプト

[解決済み】2つのオブジェクト間の一般的な深い差分

2022-03-31 13:12:32

質問

2つのオブジェクトがあります。 oldObjnewObj .

のデータは oldObj はフォームに入力するために使用され newObj は、ユーザーがこのフォームのデータを変更し、送信した結果です。

つまり、オブジェクトやオブジェクトの配列などのプロパティを持ち、その深さはn階層になります。

今、必要なのは、何が変更されたか(追加、更新、削除など)だけでなく oldObj から newObj ということではなく、どのように表現するのがベストなのか。

今までの私の考えは、ただ単に genericDeepDiffBetweenObjects メソッドで、フォーム上のオブジェクトを返します。 {add:{...},upd:{...},del:{...}} しかし、その時、私は考えました。

そこで...これを実現するライブラリやコードをご存知の方、また、(JSONシリアライズ可能な方法で)違いを表現するさらに良い方法をご存知の方はいらっしゃいませんか?

更新しました。

更新されたデータを表現するために、より良い方法を考えました。 newObj しかし、すべてのプロパティ値をフォーム上のオブジェクトに変換します。

{type: '<update|create|delete>', data: <propertyValue>}

ということは newObj.prop1 = 'new value'oldObj.prop1 = 'old value' を設定することになります。 returnObj.prop1 = {type: 'update', data: 'new value'}

アップデート2

配列のプロパティになると、本当に厄介なことになります。 [1,2,3] と等しいとみなされるはずです。 [2,3,1] 文字列やint型、bool型のような値ベースの配列では簡単ですが、オブジェクトや配列のような参照型の配列になると、扱いが非常に難しくなります。

等しく見つかるはずの配列の例。

[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]

このような深い値の等価性をチェックするのはかなり複雑なだけでなく、そうなるかもしれない変化を表現する良い方法を見つけ出すのも大変です。

どのように解決するのか?

私はあなたが望むことを行っている小さなクラスを書いたので、テストすることができます。 こちら .

ただ、あなたの提案と違うのは、私は以下のことを考慮に入れていないことです。

[1,[{c: 1},2,3],{a:'hey'}]

そして

[{a:'hey'},1,[3,{c: 1},2]]

というのは、配列は要素の順番が同じでないと等しくならないと思うからです。もちろん、これは必要に応じて変更することができます。また、このコードをさらに拡張して、渡されたプリミティブ値に基づいて diff オブジェクトを任意の方法でフォーマットするための関数を引数に取ることもできます(現在この仕事は "compareValues" メソッドによって行われています)。

var deepDiffMapper = function () {
  return {
    VALUE_CREATED: 'created',
    VALUE_UPDATED: 'updated',
    VALUE_DELETED: 'deleted',
    VALUE_UNCHANGED: 'unchanged',
    map: function(obj1, obj2) {
      if (this.isFunction(obj1) || this.isFunction(obj2)) {
        throw 'Invalid argument. Function given, object expected.';
      }
      if (this.isValue(obj1) || this.isValue(obj2)) {
        return {
          type: this.compareValues(obj1, obj2),
          data: obj1 === undefined ? obj2 : obj1
        };
      }

      var diff = {};
      for (var key in obj1) {
        if (this.isFunction(obj1[key])) {
          continue;
        }

        var value2 = undefined;
        if (obj2[key] !== undefined) {
          value2 = obj2[key];
        }

        diff[key] = this.map(obj1[key], value2);
      }
      for (var key in obj2) {
        if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
          continue;
        }

        diff[key] = this.map(undefined, obj2[key]);
      }

      return diff;

    },
    compareValues: function (value1, value2) {
      if (value1 === value2) {
        return this.VALUE_UNCHANGED;
      }
      if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
        return this.VALUE_UNCHANGED;
      }
      if (value1 === undefined) {
        return this.VALUE_CREATED;
      }
      if (value2 === undefined) {
        return this.VALUE_DELETED;
      }
      return this.VALUE_UPDATED;
    },
    isFunction: function (x) {
      return Object.prototype.toString.call(x) === '[object Function]';
    },
    isArray: function (x) {
      return Object.prototype.toString.call(x) === '[object Array]';
    },
    isDate: function (x) {
      return Object.prototype.toString.call(x) === '[object Date]';
    },
    isObject: function (x) {
      return Object.prototype.toString.call(x) === '[object Object]';
    },
    isValue: function (x) {
      return !this.isObject(x) && !this.isArray(x);
    }
  }
}();


var result = deepDiffMapper.map({
  a: 'i am unchanged',
  b: 'i am deleted',
  e: {
    a: 1,
    b: false,
    c: null
  },
  f: [1, {
    a: 'same',
    b: [{
      a: 'same'
    }, {
      d: 'delete'
    }]
  }],
  g: new Date('2017.11.25')
}, {
  a: 'i am unchanged',
  c: 'i am created',
  e: {
    a: '1',
    b: '',
    d: 'created'
  },
  f: [{
    a: 'same',
    b: [{
      a: 'same'
    }, {
      c: 'create'
    }]
  }, 1],
  g: new Date('2017.11.25')
});
console.log(result);