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;
}, {});
