Skip to content

Commit ccc1e6b

Browse files
Don't drop empty objects during merge (#1202)
1 parent 2e4c5aa commit ccc1e6b

File tree

4 files changed

+55
-13
lines changed

4 files changed

+55
-13
lines changed

packages/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(..., { merge: true })`.
79

810
# 0.7.3
911
- [changed] Changed the internal handling for locally updated documents that

packages/firestore/src/api/user_data_converter.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -539,15 +539,25 @@ export class UserDataConverter {
539539

540540
private parseObject(obj: Dict<AnyJs>, context: ParseContext): FieldValue {
541541
let result = new SortedMap<string, FieldValue>(primitiveComparator);
542-
objUtils.forEach(obj, (key: string, val: AnyJs) => {
543-
const parsedValue = this.parseData(
544-
val,
545-
context.childContextForField(key)
546-
);
547-
if (parsedValue != null) {
548-
result = result.insert(key, parsedValue);
542+
543+
if (objUtils.isEmpty(obj)) {
544+
// If we encounter an empty object, we explicitly add it to the update
545+
// mask to ensure that the server creates a map entry.
546+
if (context.path && context.path.length > 0) {
547+
context.fieldMask.push(context.path);
549548
}
550-
});
549+
} else {
550+
objUtils.forEach(obj, (key: string, val: AnyJs) => {
551+
const parsedValue = this.parseData(
552+
val,
553+
context.childContextForField(key)
554+
);
555+
if (parsedValue != null) {
556+
result = result.insert(key, parsedValue);
557+
}
558+
});
559+
}
560+
551561
return new ObjectValue(result);
552562
}
553563

packages/firestore/src/model/mutation.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,11 +449,13 @@ export class PatchMutation extends Mutation {
449449

450450
private patchObject(data: ObjectValue): ObjectValue {
451451
for (const fieldPath of this.fieldMask.fields) {
452-
const newValue = this.data.field(fieldPath);
453-
if (newValue !== undefined) {
454-
data = data.set(fieldPath, newValue);
455-
} else {
456-
data = data.delete(fieldPath);
452+
if (!fieldPath.isEmpty()) {
453+
const newValue = this.data.field(fieldPath);
454+
if (newValue !== undefined) {
455+
data = data.set(fieldPath, newValue);
456+
} else {
457+
data = data.delete(fieldPath);
458+
}
457459
}
458460
}
459461
return data;

packages/firestore/test/integration/api/database.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,34 @@ apiDescribe('Database', persistence => {
188188
});
189189
});
190190

191+
it('can merge empty object', async () => {
192+
await withTestDoc(persistence, async doc => {
193+
const accumulator = new EventsAccumulator<firestore.DocumentSnapshot>();
194+
const unsubscribe = doc.onSnapshot(accumulator.storeEvent);
195+
await accumulator
196+
.awaitEvent()
197+
.then(() => doc.set({}))
198+
.then(() => accumulator.awaitEvent())
199+
.then(docSnapshot => expect(docSnapshot.data()).to.be.deep.equal({}))
200+
.then(() => doc.set({ a: {} }, { mergeFields: ['a'] }))
201+
.then(() => accumulator.awaitEvent())
202+
.then(docSnapshot =>
203+
expect(docSnapshot.data()).to.be.deep.equal({ a: {} })
204+
)
205+
.then(() => doc.set({ b: {} }, { merge: true }))
206+
.then(() => accumulator.awaitEvent())
207+
.then(docSnapshot =>
208+
expect(docSnapshot.data()).to.be.deep.equal({ a: {}, b: {} })
209+
)
210+
.then(() => doc.get({ source: 'server' }))
211+
.then(docSnapshot => {
212+
expect(docSnapshot.data()).to.be.deep.equal({ a: {}, b: {} });
213+
});
214+
215+
unsubscribe();
216+
});
217+
});
218+
191219
it('can delete field using merge', () => {
192220
return withTestDoc(persistence, doc => {
193221
const initialData = {

0 commit comments

Comments
 (0)