diff --git a/src/firebase_functions/db_fn.py b/src/firebase_functions/db_fn.py index 410d5f8..aa7d33b 100644 --- a/src/firebase_functions/db_fn.py +++ b/src/firebase_functions/db_fn.py @@ -91,7 +91,7 @@ def _db_endpoint_handler( after = event_data["delta"] # Merge delta into data to generate an 'after' view of the data. if isinstance(before, dict) and isinstance(after, dict): - after = _util.prune_nones({**before, **after}) + after = _util.prune_nones(_util.deep_merge(before, after)) database_event_data = Change( before=before, after=after, diff --git a/src/firebase_functions/private/util.py b/src/firebase_functions/private/util.py index 9c521e9..0dd2eaa 100644 --- a/src/firebase_functions/private/util.py +++ b/src/firebase_functions/private/util.py @@ -77,6 +77,17 @@ def prune_nones(obj: dict) -> dict: return obj +def deep_merge(dict1, dict2): + result = dict1.copy() + for key, value in dict2.items(): + if isinstance(value, dict): + node = result.get(key, {}) + result[key] = deep_merge(node, value) + else: + result[key] = value + return result + + def valid_on_call_request(request: _Request) -> bool: """Validate request""" if (_on_call_valid_method(request) and diff --git a/tests/test_util.py b/tests/test_util.py index 355d0c9..e524033 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -15,7 +15,7 @@ Internal utils tests. """ from os import environ, path -from firebase_functions.private.util import firebase_config, microsecond_timestamp_conversion, nanoseconds_timestamp_conversion, is_precision_timestamp, normalize_path +from firebase_functions.private.util import firebase_config, microsecond_timestamp_conversion, nanoseconds_timestamp_conversion, is_precision_timestamp, normalize_path, deep_merge import datetime as _dt test_bucket = "python-functions-testing.appspot.com" @@ -121,3 +121,27 @@ def test_normalize_document_path(): test_path2 = "test/document" assert normalize_path(test_path2) == "test/document", ( "Failure, path should not be changed if it is already normalized.") + + +def test_toplevel_keys(): + dict1 = {"baz": {"answer": 42, "qux": "quux"}, "foo": "bar"} + dict2 = {"baz": {"answer": 33}} + result = deep_merge(dict1, dict2) + assert "foo" in result + assert "baz" in result + + +def test_nested_merge(): + dict1 = {"baz": {"answer": 42, "qux": "quux"}, "foo": "bar"} + dict2 = {"baz": {"answer": 33}} + result = deep_merge(dict1, dict2) + assert result["baz"]["answer"] == 33 + assert result["baz"]["qux"] == "quux" + + +def test_does_not_modify_originals(): + dict1 = {"baz": {"answer": 42, "qux": "quux"}, "foo": "bar"} + dict2 = {"baz": {"answer": 33}} + deep_merge(dict1, dict2) + assert dict1["baz"]["answer"] == 42 + assert dict2["baz"]["answer"] == 33