66

Below code works fine until today. But I don't know now it is not working and gives below error.Could you tell me why?

Error: Function DocumentReference.set() called with invalid data. Unsupported field value: a custom Budget object

 export class Project {
        id: string = null;
        name: string;
        budgetList?: Budget[];
    }

export class Budget {
    id: string;
    amount: number;
    contingency: number = 20;
    budgetGroup: BudgetGroup = new BudgetGroup();
    creationTime: string;
}

code:

  async create(data: DtoProject): Promise<Project> {
    try {
      const projectId: string = this.fireStore.createId();
      const budgets = this.budgetProvider.createBudgets(data.budgetList, projectId);//budgets
      const proj: Project = {
        id: data.id,
        name: data.name,
        budgetList: budgets,//here it has the error
      }
      proj.id = projectId;
      await this.fireStore.doc<Project>(`projects/${projectId}/`).set(proj));//project
      }
 }

  createBudgets(data: Budget[], projectId: string): Budget[] {
    let budgets: Budget[] = [];
    forEach(data, (d) => {
      const budgetId: string = this.fireStore.createId();
      d.id = budgetId;
      budgets.push(d);
      this.fireStore.doc<Budget>(`projects/${projectId}/budgets/${budgetId}`).set({
        id: budgetId,
        amount: d.amount,
        contingency: d.contingency,
        budgetGroup: d.budgetGroup,
        creationTime: moment().format()
      })
    })
    return budgets;
  }
Sampath
  • 63,341
  • 64
  • 307
  • 441
  • 3
    I ran into this error when passing in a json object where some field values were unexpectedly null – Gene Bo Apr 12 '18 at 20:17

8 Answers8

104

You have to transform your array of budgets into an array of pure JavaScript objects.

First step:

const budgets = arrayOfBudget.map((obj)=> {return Object.assign({}, obj)});

Second step:

const proj: Project = {
      id: data.id,
      name: data.name,
      budgetList: budgets
    }

Then you are good to go.

By the way, when developing with a language that compiles to JavaScript you cannot use custom Objects. Instead, you have to use pure JavaScript objects to save in the Firestore Database.

For example, let's say you have this class below:

export class User {
        id: string;
        name: string;
    }

And you try to execute the following code:

 const user = new User();   
 this.db.collection('users').doc().set(user)

You will get an error like:

invalid data. Data must be an object, but it was: a custom User object

Now if you try to execute this other line of code:

 this.db.collection('users').doc().set(Object.assign({}, user))

You will see that your object was saved in the database. Basically Object.assign does the same thing as:

this.db.collection('users').doc().set({id: user.id , name: user.name})

So make use of Object.assign, it will save you a lot of time.

UPDATE

As I have pointed out in a comment below, you can find what documentation says about Custom objects here. As you can see, there is a warning saying:

// Web uses JavaScript objects

Below there is a screenshot of what the documentation says.

enter image description here

  • 2
    Beautifull, It is working perfectly fine. Thank you so much. But could you tell me why it is working some arrays and some not? e.g. I have another array in the same class like this. But no errors there.Any explanation? Do I need to convert that too as you showed above? `export class Project { memberList?: Member[]; }` – Sampath Jan 08 '18 at 22:34
  • 4
    It is hard to tell since I am not seeing the rest of your code. But I strongly advise you to always convert your objects to pure JavaScript objects. – Mateus Forgiarini da Silva Jan 08 '18 at 22:41
  • 1
    You're welcome. I am glad I could help you. :) You can check what the documentation says about Custom Objects. Here it is the link: https://firebase.google.com/docs/firestore/manage-data/add-data As you can see there is a warning like: “ Web uses JavaScript objects”. Therefore make use of Object.assign. – Mateus Forgiarini da Silva Jan 08 '18 at 22:44
  • What if you have a GeoPoint type field and you want to keep the type? – bvamos Aug 30 '19 at 13:05
  • To avoid doing these "object to raw JS object" conversions time and time again, there is now a data converter class that you can use to trigger the said conversions at saving and retrieving times. Reference: https://firebase.google.com/docs/reference/js/firebase.firestore.FirestoreDataConverter – Gregordy May 03 '21 at 00:32
23

You can stringify the object and parse to object again to remove class names of the object.

const budgets = this.budgetProvider.createBudgets(JSON.parse(JSON.stringify(data.budgetList)), projectId);
MalindaK
  • 260
  • 2
  • 4
  • 1
    I tried other examples but this is the one for me that works perfectly with angularfire2. – 今際のアリス Jun 09 '19 at 09:37
  • I guessed this might be the way to fix this error too (in my own code) which is using a JS class user{}. And this is the solution that worked for me, but it seems quite odd that it is necessary. – raddevus Oct 17 '20 at 19:23
9

You can also use the spread operator like:

this.fireStore
  .doc<Project>(`projects/${projectId}/`)
    .set( {...proj} ))

It work for me

Andres Gardiol
  • 1,312
  • 15
  • 22
2

You can also use the add method with a new object copying all your values with the spread operator. This worked for me:

const user = new User();   
this.db.collection('users').add({...user});
0

It work for me

proj.budgetList = {...budgets}


await this.fireStore.doc<Project>(`projects/${projectId}/`).set({...proj}));
ush189
  • 1,342
  • 6
  • 22
  • 30
  • This is nice alternative to `JSON.parse(JSON.stringify(budgets))` (if field is undefined then it is add to object (this is the difference) and fb throws error - which chan be usefull) – Kamil Kiełczewski Jan 11 '22 at 21:30
0

To anyone using FirestoreDataConverter here's how I did it for an array of object type RecipeTag inside an object of type Recipe.

//Location where I call to add a Recipe

        const collectionRef = collection(db, collectionPath)
                             .withConverter(Recipe.firestoreConverter);
        await addDoc(collectionRef, recipe);

//Recipe model

    public static firestoreConverter: FirestoreDataConverter<Recipe> = {
        toFirestore: (recipe: Recipe) => {
            return {
                title: recipe.title,
                recipeUrl: recipe.recipeUrl,
                imageLink: recipe.imageLink,
                tags: recipe.tags.map(tag => RecipeTag.firestoreConverter.toFirestore(tag))
            };
        },
        fromFirestore: (snapshot: DocumentSnapshot<DocumentData>) => {
            const data = snapshot.data() as Recipe
            return new Recipe(data)
        }
    }

//RecipeTag model

    public static firestoreConverter: FirestoreDataConverter<RecipeTag> = {
        toFirestore: (recipe: RecipeTag) => {
            return {
                description: recipe.description,
                title: recipe.title,
            };
        },
        fromFirestore: (snapshot: DocumentSnapshot<DocumentData>) => {
            const data = snapshot.data() as RecipeTag
            return new RecipeTag(data)
        }
    }
0

I was facing the same issue when trying to add a Map directly to Firestore (v9)

The issue was:


// Firestore module v9

// I was trying to add a Map to firestore

        userInterestsMap.forEach(async (v: Map<string, number>, k: string) => {
            const modelRef = doc(db, "model", k);
            await setDoc(modelRef, {
                'data': v // here is the issue
            });
        })

Solution

        userInterestsMap.forEach(async (v: Map<string, number>, k: string) => {
            const modelRef = doc(db, "model", k);
            await setDoc(modelRef, {
                'data': Object.fromEntries(v) // here is the solution
            });
        })

So, basically, convert Map to Object

Nithin Sai
  • 840
  • 1
  • 10
  • 23
-2

Error is simply stating that data entered is invalid!

I've also faced the error from nowhere, which was because I removed the input field from interface which was supposed to get it's value and upload it in the document of some collection!

note: check your code in .ts file firebase.firestore().collection("some_name").add() / set() if some unnecessary field is there or not.

Nino Filiu
  • 16,660
  • 11
  • 54
  • 84