nodejs-firestore: Ignore undefined values rather than throw exceptions

Thanks for stopping by to let us know something could be better!

PLEASE READ: If you have a support contract with Google, please create an issue in the support console instead of filing on GitHub. This will ensure a timely response.

Please run down the following list and make sure you’ve tried the usual “quick fixes”:

If you are still having issues, please be sure to include as much information as possible:

Environment details

  • OS: Mac
  • Node.js version: 12
  • npm version: 6.13.6
  • @google-cloud/firestore version: 3.7.4"

Steps to reproduce

  1. Insert using fstore.collection(blah).doc(foo).set({"hello": undefined})

Expected

Like JSON.stringify, undefined keys are ignored.

Actual

Error: Value for argument “data” is not a valid Firestore document. Cannot use “undefined” as a Firestore value (found in field “hello”).

Explanations

undefines are a routine part of js. Not gracefully handling undefines means firestore becomes a brittle library that barfs every now and then. The developer experience could be much better.

A quick dirty workaround is doc.set(JSON.parse(JSON.stringify(val)), which is yuck. The library can handle this elegantly and preserve JSON.stringify like behaviour.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

As of today May 29, 2020, Firebase Added support for calling FirebaseFiresore.settings with { ignoreUndefinedProperties: true }. When this parameter is set, Cloud Firestore ignores undefined properties inside objects rather than rejecting the API call.

For anyone using the v9 API:

import { getFirestore, connectFirestoreEmulator, initializeFirestore } from 'firebase/firestore'; // Firebase v9+

  // Must be called before getFirestore()
  initializeFirestore(app, {
    ignoreUndefinedProperties: true
  });
  const firestore = getFirestore(app);

@nojvek Thanks for this request! This has come up a couple of times. I will talk with the team to see if this is a behavior we can support.

initializeFirestore(app, { ignoreUndefinedProperties: true });

Uncaught FirebaseError: initializeFirestore() has already been called with different options. To avoid this error, call initializeFirestore() with the same options as when it was originally called, or call getFirestore() to return the already initialized instance.

You can get around the issue where it’s already initialized by storing the admin instance in a singleton and just reusing that: https://stackoverflow.com/a/70468753/473201

I’m posting this is a work-around that if you’re for whatever reason unable to update your legacy firebase version and can’t access the ignoreUndefinedProperties argument.

Here’s a similar custom version set that does the same thing, that actually exposes more functionality allowing you to decide if you want to check also nested fields for undefined, or only the root level of the passed data.

export const ignoreUndefinedSet = async (
  // depending on your namespace & /types version, you can use firebase.firestore.<name> instead
  reference: FirebaseFirestore.DocumentReference,
  data: FirebaseFirestore.DocumentData,
  options?: FirebaseFirestore.SetOptions,
  checkNestedObjects = true,
) => {
  const isPlainObject = (val: unknown) =>
     typeof val === 'object' && val !== null && !(val instanceof Date) && !Array.isArray(val)
  const keepDefinedProperties = (
     obj: FirebaseFirestore.DocumentData,
     nestedCheck = true,
  ) =>
   Object.entries(data).reduce(
      (result, [key, value]) => (
           value === undefined
                ? result
                : (nestedCheck && isPlainObject(value))
                   ? Object.assign(result, { [key]: keepDefinedProperties(value) })
                   : Object.assign(result, { [key]: value })
       ),
      {}
  )
  const onlyDefinedProperties = keepDefinedProperties(data, checkNestedObjects)
  await reference.set(onlyDefinedProperties, { ...options })
}

So essentially these two statements are equivalent

await reference.set(data, { ignoreUndefinedProperties: true }) // newer firebase version
await ignoreUndefinedSet(reference, data) // my polyfill