Firestore Deep merging in a batch

Viewed 574

When I create a new document from my API, I would like to commit (or atomically update) a statistics collection which belongs to a root projects document:

    const increment = firebase.firestore.FieldValue.increment(1)
    let aggregation: LooseObject = {
      totalProjects: increment,
      tags: [],
      categories: [],
    }

    const project = fakeProject() // some debug func to return a dummy project object
    const projectRef = db.collection('projects').doc()
    const projectStatsRef = db.collection('projects').doc('--stats--')

    project.categories.forEach((category: string) => {
      aggregation.categories[category] = increment
    })

    project.tags.forEach((tag: string) => {
      aggregation.tags[tag] = increment
    })

    console.log(aggregation)

    const batch = db.batch()
    batch.set(projectRef, project)
    batch.set(projectStatsRef, aggregation, {
      merge: true,
    })
    batch.commit()

When I inspect the console, I get the following dump for my aggregation doc (this is the shape of the data that I would expect to see in the attached screenshot):

{
    totalProjects: NumericIncrementTransform { operand: 1 },
    tags: [
      raid: NumericIncrementTransform { operand: 1 },
      'attack-of-the-clone': NumericIncrementTransform { operand: 1 },
      'star-wars': NumericIncrementTransform { operand: 1 }
    ],
    categories: [ movie: NumericIncrementTransform { operand: 1 } ]
  }

However when the doc is committed to firestore, only the following structure appears (the tags and categories arrays are both empty) enter image description here

From my understanding, merge should perform a deep merge of all nested objects. If this is not the case, how can I achieve what I'm after?

I'm aware this type of storage is not scalable for thousnads of tags, I'm just tinkering with firestore for the first time and wanted to see how I would make this kind of thing work

1 Answers

Set with merge is not really a "deep" merge in the way you're expecting. When you pass an object with key/value pairs, Firestore will completely overwrite the fields specified by each of those keys with new data, but leave all the other fields in the document alone. So, if you have a document with fields { a, b, c }, and you do a set-with-merge on b, then b gets completely overwritten, and the other two go unchanged.

On top of that, Firestore doesn't at all support setting specific map field values that are embedded in arrays. Or in other words, there is no direct array item updates using array indexes.

If you must have this document structure, you will need a transaction to read the document, modify the array field in memory to appear how you want, then write the field back to the document. Yes, it's inconvenient, but it's the only way when you have array item updates involved.

Related