如何深度比较对象键结构(类Lodash isEqual)及优化递归取键函数
Got it, let's work through this problem exactly as you described. You need a cleaner way to extract all deep keys from an object, fix the null detection issue, and then use that to compare key structures (not values) to avoid unnecessary database updates.
Optimized getKeysDeep Function
First, here's the revised function that eliminates the need for an initial empty array and properly handles null:
function getKeysDeep(obj) { // Handle null and non-object primitives first (fixes the typeof null quirk) if (obj === null || typeof obj !== 'object') { return []; } // Start with the current object's keys let keys = Object.keys(obj); // Recursively add keys from nested objects keys.forEach(key => { keys = keys.concat(getKeysDeep(obj[key])); }); return keys; }
Key Improvements:
- No initial array required: Just call
getKeysDeep(yourObject)directly—no moregetKeysDeep([], yourObject)clutter. - Proper null handling: The first check explicitly skips
null(since JavaScript'stypeof nullincorrectly returns"object") and any non-object values (strings, numbers, booleans), so we never try to extract keys from invalid targets. - Simplified recursion: We build the key list internally by concatenating results from nested calls, making the function self-contained and easier to read.
Using This for Structure Comparison
Now let's update your verifyDataIntegrity function to use this key extraction instead of _.isEqual, so we only update the database when the key structure changes (not when user values differ):
async verifyDataIntegrity(id, data) { const defaultData = _.merge(new DefaultObject(id)); // Remove the deprecated field from default structure if (defaultData.hasOwnProperty('myoldsetting')) delete defaultData.myoldsetting; // Get sorted deep keys for both objects (sort to ignore insertion order differences) const dataKeys = getKeysDeep(data).sort(); const defaultKeys = getKeysDeep(defaultData).sort(); // Only update if key structures don't match if (!_.isEqual(dataKeys, defaultKeys)) { // Merge user's existing values into the default structure to preserve customizations const updatedData = _.merge({}, defaultData, data); await myMongoDBCollection.replaceOne({ id }, updatedData); return updatedData; } else { return data; } }
Why This Works Better:
- Avoids unnecessary updates: We only care about the presence/absence of keys, not their values. So if a user changed
myobj1.setting1tofalse, the key structure is still the same, and we skip the database call. - Preserves user data: Using
_.merge(defaultData, data)ensures we add new keys from the default, remove deprecated ones (likemyoldsetting), but keep all the user's custom values intact. - Order-agnostic comparison: Sorting the key arrays ensures we don't get false mismatches just because the default object and user data have keys in different insertion orders.
Bonus: Precise Nested Key Paths (Optional)
If you want even more clarity (e.g., to debug exactly which nested key is missing), you can modify the function to return full dot-separated paths instead of flat keys:
function getDeepKeyPaths(obj, parentPath = '') { if (obj === null || typeof obj !== 'object') { return []; } return Object.keys(obj).flatMap(key => { const currentPath = parentPath ? `${parentPath}.${key}` : key; return [currentPath, ...getDeepKeyPaths(obj[key], currentPath)]; }); }
This returns keys like ["id", "myobj1", "myobj1.setting1", "myobj1.setting2", "myobj2", "myobj2.perm1"], making it trivial to spot exactly where the structure differs.
内容的提问来源于stack exchange,提问作者FireController1847

