Skip to content

fix: detect numeric type changes (int/float) when ignore_order=True#580

Open
frankgoldfish wants to merge 1 commit intoqlustered:masterfrom
frankgoldfish:fix/ignore-order-type-changes
Open

fix: detect numeric type changes (int/float) when ignore_order=True#580
frankgoldfish wants to merge 1 commit intoqlustered:masterfrom
frankgoldfish:fix/ignore-order-type-changes

Conversation

@frankgoldfish
Copy link

Summary

Fixes #485

DeepDiff([{'a': 1}], [{'a': 1.0}], ignore_order=True)
# Before fix: {}   ← bug: type change silently dropped
# After fix:  {'type_changes': {"root[0]['a']" : {old_type: int, new_type: float, ...}}}

Root Cause

Python's numeric equality semantics (hash(1) == hash(1.0) and 1 == 1.0) caused int 1 and float 1.0 to map to the same slot in the shared DeepHash cache dictionary. Consequently, {'a': 1} and {'a': 1.0} received identical deephashes and landed in the hash intersection — items DeepDiff considers equal — so the type difference was never reported.

Fix

Added a post-pass in _diff_iterable_with_deephash that re-examines every intersection pair when ignore_numeric_type_changes=False. A new helper _items_are_type_equal() performs a type-strict equality check (same type tree AND same values). If the pair is not truly type-equal, a full _diff() is run on it, surfacing any nested type_changes.

Key properties of the fix:

  • Only touches the ignore_order code path — zero change to deephash.py
  • Zero overhead when all intersection items are genuinely equal
  • Fully respects ignore_numeric_type_changes=True (post-pass is skipped)
  • Introduces no new test failures (23 pre-existing failures unchanged)

Tests

Added TestIgnoreOrderNumericTypeChange in tests/test_ignore_order.py with 6 targeted cases covering: the core regression, the ignore_numeric_type_changes=True suppression, value-only changes, reorder no-op, mixed lists, and the ignore_order=False path.

Fixes qlustered#485

When ignore_order=True, DeepDiff([{'a': 1}], [{'a': 1.0}]) incorrectly
returned {} instead of reporting a type_change for the int→float difference.

Root cause
----------
Python's numeric equality semantics (hash(1) == hash(1.0) and 1 == 1.0)
caused int 1 and float 1.0 to map to the same slot in the shared DeepHash
cache dictionary.  As a result, {'a': 1} and {'a': 1.0} received the same
deephash, landed in the hash *intersection*, and were silently treated as
identical items — so the type difference was never reported.

Fix
---
After the existing hash-intersection logic in _diff_iterable_with_deephash,
add a post-pass that re-examines every pair of items whose hashes are equal
(i.e. in the intersection) when ignore_numeric_type_changes is False.  A
lightweight type-strict equality helper _items_are_type_equal() checks
whether the pair is truly identical (same type tree and values).  If not,
a full _diff() is run on the pair, which surfaces any nested type_changes.

This approach:
- Touches only the ignore_order code path (no change to deephash.py)
- Adds zero overhead when all intersection items are genuinely equal
- Fully respects ignore_numeric_type_changes=True (no post-pass in that case)
- Passes all pre-existing tests (23 failures are pre-existing, not introduced)

Example
-------
Before fix:
    DeepDiff([{'a': 1}], [{'a': 1.0}], ignore_order=True)
    # {}  ← wrong

After fix:
    DeepDiff([{'a': 1}], [{'a': 1.0}], ignore_order=True)
    # {'type_changes': {"root[0]['a']": {old_type: int, new_type: float, ...}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ignore_order=True stops flagging number type changes

1 participant