Introduction
Comparing two objects to find property differences is common in change tracking, audit logging, unit testing, and data synchronization. The approach depends on your language: JavaScript uses Object.keys with iteration, Python uses __dict__ comparison or the deepdiff library, C# uses reflection, and Java uses reflection or EqualsBuilder. Each method detects which properties changed, what the old and new values are, and whether properties were added or removed.
JavaScript
1function findDifferences(obj1, obj2) {
2 const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
3 const diffs = {};
4
5 for (const key of allKeys) {
6 if (obj1[key] !== obj2[key]) {
7 diffs[key] = { old: obj1[key], new: obj2[key] };
8 }
9 }
10 return diffs;
11}
12
13const before = { name: "Alice", age: 30, city: "NYC" };
14const after = { name: "Alice", age: 31, city: "Boston", email: "[email protected]" };
15
16console.log(findDifferences(before, after));
17// { age: { old: 30, new: 31 }, city: { old: "NYC", new: "Boston" }, email: { old: undefined, new: "[email protected]" } }
This shallow comparison works for primitive values. For nested objects, use deep comparison.
JavaScript Deep Comparison
1function deepDiff(obj1, obj2, path = '') {
2 const diffs = [];
3
4 const allKeys = new Set([
5 ...Object.keys(obj1 || {}),
6 ...Object.keys(obj2 || {}),
7 ]);
8
9 for (const key of allKeys) {
10 const fullPath = path ? `${path}.${key}` : key;
11 const val1 = obj1?.[key];
12 const val2 = obj2?.[key];
13
14 if (typeof val1 === 'object' && typeof val2 === 'object' && val1 !== null && val2 !== null) {
15 diffs.push(...deepDiff(val1, val2, fullPath));
16 } else if (val1 !== val2) {
17 diffs.push({ path: fullPath, old: val1, new: val2 });
18 }
19 }
20 return diffs;
21}
22
23const a = { user: { name: "Alice", address: { city: "NYC" } } };
24const b = { user: { name: "Alice", address: { city: "Boston" } } };
25
26console.log(deepDiff(a, b));
27// [{ path: "user.address.city", old: "NYC", new: "Boston" }]
Python
1# Simple dict comparison
2def find_differences(obj1, obj2):
3 all_keys = set(obj1.keys()) | set(obj2.keys())
4 diffs = {}
5 for key in all_keys:
6 val1 = obj1.get(key)
7 val2 = obj2.get(key)
8 if val1 != val2:
9 diffs[key] = {"old": val1, "new": val2}
10 return diffs
11
12before = {"name": "Alice", "age": 30, "city": "NYC"}
13after = {"name": "Alice", "age": 31, "city": "Boston"}
14print(find_differences(before, after))
15# {'age': {'old': 30, 'new': 31}, 'city': {'old': 'NYC', 'new': 'Boston'}}
16
17# Using deepdiff for complex objects
18from deepdiff import DeepDiff
19
20diff = DeepDiff(before, after)
21print(diff)
22# {'values_changed': {"root['age']": {'new_value': 31, 'old_value': 30}, ...}}
The deepdiff library handles nested objects, lists, sets, and type changes automatically.
C# with Reflection
1public static Dictionary<string, (object OldValue, object NewValue)> FindDifferences<T>(T obj1, T obj2)
2{
3 var diffs = new Dictionary<string, (object, object)>();
4 var properties = typeof(T).GetProperties();
5
6 foreach (var prop in properties)
7 {
8 var val1 = prop.GetValue(obj1);
9 var val2 = prop.GetValue(obj2);
10
11 if (!Equals(val1, val2))
12 {
13 diffs[prop.Name] = (val1, val2);
14 }
15 }
16 return diffs;
17}
18
19// Usage
20var before = new User { Name = "Alice", Age = 30, City = "NYC" };
21var after = new User { Name = "Alice", Age = 31, City = "Boston" };
22
23var diffs = FindDifferences(before, after);
24foreach (var diff in diffs)
25{
26 Console.WriteLine($"{diff.Key}: {diff.Value.OldValue} -> {diff.Value.NewValue}");
27}
28// Age: 30 -> 31
29// City: NYC -> Boston
Java with Reflection
1import java.lang.reflect.Field;
2import java.util.*;
3
4public class ObjectDiff {
5 public static Map<String, Object[]> findDifferences(Object obj1, Object obj2) throws Exception {
6 Map<String, Object[]> diffs = new HashMap<>();
7 Field[] fields = obj1.getClass().getDeclaredFields();
8
9 for (Field field : fields) {
10 field.setAccessible(true);
11 Object val1 = field.get(obj1);
12 Object val2 = field.get(obj2);
13
14 if (!Objects.equals(val1, val2)) {
15 diffs.put(field.getName(), new Object[]{val1, val2});
16 }
17 }
18 return diffs;
19 }
20}
21
22// Usage
23User before = new User("Alice", 30, "NYC");
24User after = new User("Alice", 31, "Boston");
25Map<String, Object[]> diffs = ObjectDiff.findDifferences(before, after);
26// age: [30, 31], city: [NYC, Boston]
Using Libraries
1// Lodash (JavaScript)
2const _ = require('lodash');
3
4function findDiffs(obj1, obj2) {
5 return _.reduce(obj1, (result, value, key) => {
6 if (!_.isEqual(value, obj2[key])) {
7 result[key] = { old: value, new: obj2[key] };
8 }
9 return result;
10 }, {});
11}
1# deepdiff (Python) — handles nested objects, type changes, list reordering
2from deepdiff import DeepDiff
3
4t1 = {"a": 1, "b": [1, 2, 3], "c": {"x": 10}}
5t2 = {"a": 2, "b": [1, 3, 2], "c": {"x": 10, "y": 20}}
6
7diff = DeepDiff(t1, t2, ignore_order=True)
8print(diff)
Common Pitfalls
Shallow comparison on nested objects: Using === or == on objects compares references, not contents. Two objects with identical properties are not equal by reference. Always recurse into nested objects or use deep comparison libraries.
Missing keys treated as undefined vs absent: In JavaScript, obj.missing returns undefined, which may falsely match a property explicitly set to undefined. Use hasOwnProperty to distinguish missing from undefined.
Reflection performance in hot paths: Reflection-based comparison in C# and Java is slow compared to manual property checks. Cache PropertyInfo/Field arrays and avoid reflection in loops processing thousands of objects.
Circular references causing infinite recursion: Objects that reference each other (A.parent = B, B.child = A) cause stack overflow in recursive comparison. Track visited objects in a Set or use libraries with built-in circular reference detection.
Ignoring type coercion: In JavaScript, 1 == "1" is true with loose equality. Always use strict equality (===) when comparing property values to avoid false "no difference" results.
Summary
JavaScript: iterate Object.keys with === for shallow, recurse for deep, or use Lodash _.isEqual
Python: compare __dict__ for simple objects, use deepdiff for nested structures
C#: use GetProperties() reflection to compare all public properties
Java: use getDeclaredFields() reflection with Objects.equals() for null safety
For production use, prefer tested libraries (deepdiff, Lodash, Apache Commons) over hand-rolled comparison