Skip to content

Commit f1a2d0a

Browse files
Don't drop empty objects during merge (#26)
1 parent 351df0e commit f1a2d0a

File tree

4 files changed

+52
-13
lines changed

4 files changed

+52
-13
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
- [changed] Changed `get()` to only make 1 attempt to reach the backend before
55
returning cached data, potentially reducing delays while offline. Previously
66
it would make 2 attempts, to work around a backend bug.
7+
- [fixed] Fixed an issue that caused us to drop empty objects from calls to
8+
`set(..., SetOptions.merge())`.
79
- [changed] Some SDK errors that represent common mistakes (such as permission
810
denied or a missing index) will automatically be logged as a warning in
911
addition to being surfaced via the API.

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/FirestoreTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
import com.google.firebase.Timestamp;
4040
import com.google.firebase.firestore.FirebaseFirestoreException.Code;
4141
import com.google.firebase.firestore.Query.Direction;
42+
import com.google.firebase.firestore.testutil.EventAccumulator;
4243
import com.google.firebase.firestore.testutil.IntegrationTestUtil;
4344
import com.google.firebase.firestore.util.AsyncQueue.TimerId;
4445
import java.util.Arrays;
46+
import java.util.Collections;
4547
import java.util.Date;
4648
import java.util.List;
4749
import java.util.Map;
@@ -110,6 +112,31 @@ public void testCanMergeServerTimestamps() {
110112
assertTrue(doc.get("nested.time") instanceof Timestamp);
111113
}
112114

115+
@Test
116+
public void testCanMergeEmptyObject() {
117+
DocumentReference documentReference = testDocument();
118+
EventAccumulator<DocumentSnapshot> eventAccumulator = new EventAccumulator<>();
119+
ListenerRegistration listenerRegistration =
120+
documentReference.addSnapshotListener(eventAccumulator.listener());
121+
eventAccumulator.await();
122+
123+
documentReference.set(Collections.emptyMap());
124+
DocumentSnapshot snapshot = eventAccumulator.await();
125+
assertEquals(Collections.emptyMap(), snapshot.getData());
126+
127+
waitFor(documentReference.set(map("a", Collections.emptyMap()), SetOptions.mergeFields("a")));
128+
snapshot = eventAccumulator.await();
129+
assertEquals(map("a", Collections.emptyMap()), snapshot.getData());
130+
131+
waitFor(documentReference.set(map("b", Collections.emptyMap()), SetOptions.merge()));
132+
snapshot = eventAccumulator.await();
133+
assertEquals(map("a", Collections.emptyMap(), "b", Collections.emptyMap()), snapshot.getData());
134+
135+
snapshot = waitFor(documentReference.get(Source.SERVER));
136+
assertEquals(map("a", Collections.emptyMap(), "b", Collections.emptyMap()), snapshot.getData());
137+
listenerRegistration.remove();
138+
}
139+
113140
@Test
114141
public void testCanDeleteFieldUsingMerge() {
115142
DocumentReference documentReference = testCollection("rooms").document("eros");

firebase-firestore/src/main/java/com/google/firebase/firestore/UserDataConverter.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -258,15 +258,23 @@ private FieldValue parseData(Object input, ParseContext context) {
258258

259259
private <K, V> ObjectValue parseMap(Map<K, V> map, ParseContext context) {
260260
Map<String, FieldValue> result = new HashMap<>();
261-
for (Entry<K, V> entry : map.entrySet()) {
262-
if (!(entry.getKey() instanceof String)) {
263-
throw context.createError(
264-
String.format("Non-String Map key (%s) is not allowed", entry.getValue()));
261+
262+
if (map.isEmpty()) {
263+
if (context.getPath() != null && !context.getPath().isEmpty()) {
264+
context.addToFieldMask(context.getPath());
265265
}
266-
String key = (String) entry.getKey();
267-
@Nullable FieldValue parsedValue = parseData(entry.getValue(), context.childContext(key));
268-
if (parsedValue != null) {
269-
result.put(key, parsedValue);
266+
return ObjectValue.emptyObject();
267+
} else {
268+
for (Entry<K, V> entry : map.entrySet()) {
269+
if (!(entry.getKey() instanceof String)) {
270+
throw context.createError(
271+
String.format("Non-String Map key (%s) is not allowed", entry.getValue()));
272+
}
273+
String key = (String) entry.getKey();
274+
@Nullable FieldValue parsedValue = parseData(entry.getValue(), context.childContext(key));
275+
if (parsedValue != null) {
276+
result.put(key, parsedValue);
277+
}
270278
}
271279
}
272280
return ObjectValue.fromMap(result);

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/PatchMutation.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,13 @@ private ObjectValue patchDocument(@Nullable MaybeDocument maybeDoc) {
147147

148148
private ObjectValue patchObject(ObjectValue obj) {
149149
for (FieldPath path : mask.getMask()) {
150-
FieldValue newValue = value.get(path);
151-
if (newValue == null) {
152-
obj = obj.delete(path);
153-
} else {
154-
obj = obj.set(path, newValue);
150+
if (!path.isEmpty()) {
151+
FieldValue newValue = value.get(path);
152+
if (newValue == null) {
153+
obj = obj.delete(path);
154+
} else {
155+
obj = obj.set(path, newValue);
156+
}
155157
}
156158
}
157159
return obj;

0 commit comments

Comments
 (0)