ค้นหาเว็บไซต์

javascript - วิธีการเปรียบเทียบเชิงลึกระหว่างวัตถุ 2 ตัวด้วย lodash?


ฉันมีวัตถุที่ซ้อนกัน 2 วัตถุซึ่งแตกต่างกัน และฉันต้องการทราบว่าพวกมันมีความแตกต่างในคุณสมบัติที่ซ้อนกันของพวกมันหรือไม่

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

วัตถุนี้อาจซับซ้อนกว่านี้มากด้วยคุณสมบัติที่ซ้อนกันมากขึ้น แต่ตัวนี้เป็นตัวอย่างที่ดี ฉันมีตัวเลือกที่จะใช้ฟังก์ชันแบบเรียกซ้ำหรือบางอย่างกับ lodash...

คำตอบ #1

วิธีแก้ปัญหาที่ง่ายและสง่างามคือการใช้ _.isEqual ซึ่งทำการเปรียบเทียบเชิงลึก:

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

console.log(_.isEqual(a, b)); // returns false if different
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

อย่างไรก็ตาม วิธีแก้ปัญหานี้ไม่แสดงว่าคุณสมบัติใดแตกต่างกัน

คำตอบ #2

หากคุณต้องการทราบว่าสมบัติใดแตกต่างกัน ให้ใช้ reduce():

_.reduce(a, function(result, value, key) {
    return _.isEqual(value, b[key]) ?
        result : result.concat(key);
}, []);
// → [ "prop2" ]

คำตอบ #3

สำหรับใครก็ตามที่เข้ามาพบเธรดนี้ นี่คือวิธีแก้ปัญหาที่สมบูรณ์มากขึ้น มันจะเปรียบเทียบ สองวัตถุ และให้คีย์ของคุณสมบัติทั้งหมดที่ มีเฉพาะใน object1, มีเฉพาะใน object2, หรือ ทั้งใน object1 และ object2 แต่มีค่าต่างกัน:

/*
 * Compare two objects by reducing an array of keys in obj1, having the
 * keys in obj2 as the intial value of the result. Key points:
 *
 * - All keys of obj2 are initially in the result.
 *
 * - If the loop finds a key (from obj1, remember) not in obj2, it adds
 *   it to the result.
 *
 * - If the loop finds a key that are both in obj1 and obj2, it compares
 *   the value. If it's the same value, the key is removed from the result.
 */
function getObjectDiff(obj1, obj2) {
    const diff = Object.keys(obj1).reduce((result, key) => {
        if (!obj2.hasOwnProperty(key)) {
            result.push(key);
        } else if (_.isEqual(obj1[key], obj2[key])) {
            const resultKeyIndex = result.indexOf(key);
            result.splice(resultKeyIndex, 1);
        }
        return result;
    }, Object.keys(obj2));

    return diff;
}

นี่คือตัวอย่างผลลัพธ์:

// Test
let obj1 = {
    a: 1,
    b: 2,
    c: { foo: 1, bar: 2},
    d: { baz: 1, bat: 2 }
}

let obj2 = {
    b: 2, 
    c: { foo: 1, bar: 'monkey'}, 
    d: { baz: 1, bat: 2 }
    e: 1
}
getObjectDiff(obj1, obj2)
// ["c", "e", "a"]

หากคุณไม่สนใจเกี่ยวกับอ็อบเจ็กต์ซ้อนและต้องการข้าม lodash คุณสามารถแทนที่ _.isEqual ด้วยการเปรียบเทียบค่าปกติ เช่น obj1[key] === obj2[key].

คำตอบ #4

อิงจากคำตอบของ Adam Boduch ฉันได้เขียนฟังก์ชันนี้ซึ่ง เปรียบเทียบวัตถุสองตัวในเชิงลึกที่สุด โดยจะคืนค่าเส้นทางที่มีค่าต่างกันรวมถึงเส้นทางที่ขาดหายไปในวัตถุหนึ่งหรือวัตถุอีกตัว

โค้ดนี้ไม่ได้ถูกเขียนขึ้นโดยคำนึงถึงประสิทธิภาพ และการปรับปรุงในด้านนั้นย่อมยินดีเป็นอย่างยิ่ง แต่ต่อไปนี้คือรูปแบบพื้นฐาน:

var compare = function (a, b) {

  var result = {
    different: [],
    missing_from_first: [],
    missing_from_second: []
  };

  _.reduce(a, function (result, value, key) {
    if (b.hasOwnProperty(key)) {
      if (_.isEqual(value, b[key])) {
        return result;
      } else {
        if (typeof (a[key]) != typeof ({}) || typeof (b[key]) != typeof ({})) {
          //dead end.
          result.different.push(key);
          return result;
        } else {
          var deeper = compare(a[key], b[key]);
          result.different = result.different.concat(_.map(deeper.different, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_second = result.missing_from_second.concat(_.map(deeper.missing_from_second, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_first = result.missing_from_first.concat(_.map(deeper.missing_from_first, (sub_path) => {
            return key + "." + sub_path;
          }));
          return result;
        }
      }
    } else {
      result.missing_from_second.push(key);
      return result;
    }
  }, result);

  _.reduce(b, function (result, value, key) {
    if (a.hasOwnProperty(key)) {
      return result;
    } else {
      result.missing_from_first.push(key);
      return result;
    }
  }, result);

  return result;
}

คุณสามารถลองโค้ดโดยใช้โค้ดตัวอย่างนี้ (แนะนำให้รันในโหมดเต็มหน้า):

var compare = function (a, b) {

  var result = {
    different: [],
    missing_from_first: [],
    missing_from_second: []
  };

  _.reduce(a, function (result, value, key) {
    if (b.hasOwnProperty(key)) {
      if (_.isEqual(value, b[key])) {
        return result;
      } else {
        if (typeof (a[key]) != typeof ({}) || typeof (b[key]) != typeof ({})) {
          //dead end.
          result.different.push(key);
          return result;
        } else {
          var deeper = compare(a[key], b[key]);
          result.different = result.different.concat(_.map(deeper.different, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_second = result.missing_from_second.concat(_.map(deeper.missing_from_second, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_first = result.missing_from_first.concat(_.map(deeper.missing_from_first, (sub_path) => {
            return key + "." + sub_path;
          }));
          return result;
        }
      }
    } else {
      result.missing_from_second.push(key);
      return result;
    }
  }, result);

  _.reduce(b, function (result, value, key) {
    if (a.hasOwnProperty(key)) {
      return result;
    } else {
      result.missing_from_first.push(key);
      return result;
    }
  }, result);

  return result;
}

var a_editor = new JSONEditor($('#a')[0], {
  name: 'a',
  mode: 'code'
});
var b_editor = new JSONEditor($('#b')[0], {
  name: 'b',
  mode: 'code'
});

var a = {
  same: 1,
  different: 2,
  missing_from_b: 3,
  missing_nested_from_b: {
    x: 1,
    y: 2
  },
  nested: {
    same: 1,
    different: 2,
    missing_from_b: 3
  }
}

var b = {
  same: 1,
  different: 99,
  missing_from_a: 3,
  missing_nested_from_a: {
    x: 1,
    y: 2
  },
  nested: {
    same: 1,
    different: 99,
    missing_from_a: 3
  }
}

a_editor.set(a);
b_editor.set(b);

var result_editor = new JSONEditor($('#result')[0], {
  name: 'result',
  mode: 'view'
});

var do_compare = function() {
  var a = a_editor.get();
  var b = b_editor.get();
  result_editor.set(compare(a, b));
}
#objects {} #objects section {
  margin-bottom: 10px;
}
#objects section h1 {
  background: #444;
  color: white;
  font-family: monospace;
  display: inline-block;
  margin: 0;
  padding: 5px;
}
.jsoneditor-outer, .ace_editor {
min-height: 230px !important;
}
button:hover {
  background: orangered;
}
button {
  cursor: pointer;
  background: red;
  color: white;
  text-align: left;
  font-weight: bold;
  border: 5px solid crimson;
  outline: 0;
  padding: 10px;
  margin: 10px 0px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="objects">
  <section>
    <h1>a (first object)</h1>
    <div id="a"></div>
  </section>
  <section>
    <h1>b (second object)</h1>
    <div id="b"></div>
  </section>
  <button onClick="do_compare()">compare</button>
  <section>
    <h1>result</h1>
    <div id="result"></div>
  </section>
</div>

คำตอบ #5

นี่คือวิธีแก้ปัญหาแบบสรุปโดยใช้ Lodash:

_.differenceWith(a, b, _.isEqual);

โปรดทราบว่าทั้งสองอินพุตต้องเป็นอาเรย์ (อาจเป็นอาเรย์ที่มีวัตถุเพียงหนึ่งชิ้น)

คำตอบ #6

ในการแสดงอย่างซ้ำ ๆ ว่าวัตถุต่างจากวัตถุอื่นอย่างไร คุณสามารถใช้ _.reduce ร่วมกับ _.isEqual และ _.isPlainObject ในกรณีนี้คุณสามารถเปรียบเทียบได้ว่า a แตกต่างจาก b อย่างไร หรือ b แตกต่างจาก a อย่างไร:

const objectA = {
  a: {
    1: "SAME WILL BE MISSING IN RESULT", 
    2: "BBB", 
    3: [1, 2, 3]
  }, 
  b: "not", 
  c: "foo bar"
};
const objectB = {
  a: {
    1: "SAME WILL BE MISSING IN RESULT",
    2: [1, 2]
  }, 
  b: "foo", 
  c: "bar"
};

const diff = function(obj1, obj2) {
  return _.reduce(obj1, function(result, value, key) {
if (_.isPlainObject(value)) {
  result[key] = diff(value, obj2[key]);
} else if (!_.isEqual(value, obj2[key])) {
  result[key] = value;
}
return result;
  }, {});
};

const diffAOverB = diff(objectA, objectB);
const diffBOverA = diff(objectB, objectA);
console.log(diffAOverB);
console.log(diffBOverA);
<script src="https://cdn.jsdelivr.net/npm/[email /lodash.min.js"></script>

คำตอบ #7

โค้ดนี้จะส่งกลับวัตถุที่มีคุณสมบัติทั้งหมดซึ่งมีค่าต่างกันและยังรวมถึงค่าของวัตถุทั้งสอง ใช้ได้ดีสำหรับการบันทึกความแตกต่าง

var allkeys = _.union(_.keys(obj1), _.keys(obj2));
var difference = _.reduce(allkeys, function (result, key) {
  if ( !_.isEqual(obj1[key], obj2[key]) ) {
    result[key] = {obj1: obj1[key], obj2: obj2[key]}
  }
  return result;
}, {});

บทความที่เกี่ยวข้อง