Handling undefined Values with Firebase - javascript

I have a simple form in my web application that persists a JSON object to my Firebase database. Not every field on this form is required, so when the object being sent is constructed, it may have several undefined properties.
Being that Firebase throws an error when trying to persist undefined values, (not to mention, you should not have an undefined value on a JSON object), I'm wondering if there's any method provided by the framework to override all undefined values to null or even an empty string; I've yet to find a solution online.
Worst case scenario I could set all undefined properties to null manually but I'd rather not do that
Any ideas or suggestions on this?
Thanks much!

For those using Firestore, it's now possible to ignore undefined fields: https://github.com/googleapis/nodejs-firestore/issues/1031#issuecomment-636308604
elmasry: 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.
Example:
firebase.firestore().settings({
ignoreUndefinedProperties: true,
})
await firebase
.firestore()
.collection('products')
.doc(productId)
.update({
name: undefined, // Won't throw an error
})

My solution was to simply stringify() the object combined with a replacer to replace all undefined values
obj = JSON.parse(JSON.stringify(obj, function(k, v) {
if (v === undefined) { return null; } return v;
}));
Then I convert it back to an object with JSON parse()

As of the breaking changes in Firebase 9 JS SDK it can be written like this:
import {initializeApp} from 'firebase/app';
import {
initializeFirestore,
} from 'firebase/firestore/lite';
export const FirebaseConfig = {
apiKey: 'example',
authDomain: 'example.firebaseapp.com',
projectId: 'example',
storageBucket: 'example.appspot.com',
messagingSenderId: 'example',
appId: 'example',
measurementId: 'example',
};
const app = initializeApp(FirebaseConfig);
initializeFirestore(app, {
ignoreUndefinedProperties: true,
});

I don't believe there are any particular methods for this due to the nature of JSON expecting a value to be passed in, which in this case could be 'null' or an empty string.
As unfortunate as this may be at the moment, I would think it would be better to handle this at the client end and manually add 'null' or an empty string if no data is entered. You can then later test data at that location in Firebase using something like 'snapshot.val() !== null'.

TL;DR: Can a value be undefined||null? fbRef.set(JSON.stringify(obj))/fbRef.once(snap=>{const obj=JSON.parse(snap.val())}), otherwise as usual.
To store a JSON object with undefined/unknown values, stringify it and store it to Firebase, then parse to recover it. This will avoid undesired/ unforeseen sets/update errors.
If the object is part of your firebase paths, then proceed with Kurai's solution. Be aware that Firebase will not store a key with null or undefined as value, then if you want to update it later, it will fail. If you want the path to remain after changes or to maintain such keys, set them to 0 (Be aware of the side-effects).

For someone searching for generic solution to clean out null & undefined values this might help.
create a helper function like this and wrap the object you want to clean.
const Obj = {
key: 'value',
key2: null, // Warning: null values would be preserved
key3: undefined
}
function cleanObj(object) {
return JSON.parse(JSON.stringify(object))
}
const test = cleanObj(Obj)
console.log(test)

Related

When do javascript GCP libraries return null?

I'm working with GCP and Firebase using typescript. I'm using provided libraries (v8 in case of firebase) and I have noticed some weird behaviour
For example (firebase, ver. 8.10.1)
import 'firebase/auth'
import firebase from 'firebase/app'
firebase.initializeApp({ apiKey: 'my-api-key', projectId: 'my-project-id' })
const auth = firebase.auth()
const { user } = await auth.signInWithEmailAndPassword('example#email.com', 'example-password')
signInWithEmailAndPassword method returns an object of type UserCredential with all properties typed Type | null
type UserCredential = {
additionalUserInfo?: firebase.auth.AdditionalUserInfo | null;
credential: firebase.auth.AuthCredential | null;
operationType?: string | null;
user: firebase.User | null;
};
Where is this null coming from? Based on my testing, these methods always return proper values or reject the promise with an error. I can't find a documented case where null is returned
The same with other libraries (secret-manager, ver. 4.2.0)
import { SecretManagerServiceClient } from '#google-cloud/secret-manager'
const secretManager = new SecretManagerServiceClient()
const [secretVersion] = await secretManager.accessSecretVersion({
name: 'projects/p/secrets/secret-name/versions/latest'
})
Here again, return type looks like:
interface IAccessSecretVersionResponse {
name?: string | null;
payload?: google.cloud.secretmanager.v1.ISecretPayload | null;
}
Where are those null values coming from? Under which circumstances could these libraries return null instead of an error?
Can someone explain this behaviour or point me to the correct place in the docs?
I think you are asking this because you are having typescript intellisense issues while assigning the values returned by this library functions. And getting error something like in the IDE Type 'X | null' is not assignable to type 'X'. Type 'null' is not assignable to type 'X'.
The use of the Type | null in the return type for UserCredential and IAccessSecretVersionResponse is due to the possibility that the API may return null if the request fails or the data is not available.
Now as you have mentioned, these methods always return proper values or reject the promise with an error. But there are other circumstances in which it will return null:
Incorrect API usage: If the API is used in an incorrect manner, such as passing in an invalid parameter, it may return null.
Network errors: If the API call encounters a network error, it may return null.
Data not found: If the API cannot find the requested data, it may return null.
Authorization errors: If the API call requires authorization and the provided credentials are invalid, it may return null.
While building any method for any library devs provide what will be expected returned, Corresponding Errors and null just for anything happens other than expected result and Errors like mentioned above.
This issue seems to be resolved or non-existence in firebase V9 (modular SDK) version.
The Official Reference for these methods are signInWithEmailAndPassword which returns Promise of UserCredential and same with SecretManagerServiceClient.

Firestore - Store a property id is reference type by firestore Batch transaction

I trying to store data content reference type by batch transaction, then I got an exception:
Function WriteBatch.set() called with invalid data. Unsupported field value: a custom object (found in document orders/OC9dZErupEhPsamp8QEd)
Is there a way we can use batch transaction to store reference type?
this is my code:
batch.update(orderRef, {
userId: firestore.doc(userId),
});
Normaly update() use to update existing firestore data. Review firestore docs for the same. In that given example they are updating population by increments of value or with new population number but before passing it in each update function values are stored in one cost value if it is not static value. as Asked by #dharmaraj please edit your questions by posting with full code you can also read given firestore documentation for your own studies.
import firebase from "firebase/app";
const app = firebase.initializeApp({});
const firestore = app.firestore();
const batch = firestore.batch();
const newUserId = firestore.doc(userId);
batch.update(orderRef, {
userId: newUserId,
});
Log newUserId value and see what are you getting into it.
You can't store the reference object that doc() returns, it's an object that may have circular references and functions in it. doc() is not the id of the document. If you want to get the id (which is a string), then:
const newUserId = firestore.doc(userId).ref.id;
batch.update(orderRef, {
userId: newUserId,
});
I don't know why batch validate input should be a pure object. I tried to push reference type id inside nested object then it work well, yeah I know it already is a trick, but it work.
change:
batch.update(docRef, {
user: firestore.collection('users').doc(userId)
})
to:
batch.update(docRef, {
user: {
id: firestore.collection('users').doc(userId)
}
})

I can't figure out how to work with the object returned by Firebase get query

I'm building a React Native app with Expo and integrating Firebase (real time database) which I know has some limitations (https://docs.expo.dev/guides/using-firebase/). I'm trying to get a snapshot of the data using await get(query(... and have successfully done so, but I can't figure out exactly what I'm working with. When I console.log it I get this:
Object {
"key1": value1,
"key2": value2,
}
I was trying to return an array of the keys using Object.keys() but it returns this:
Array [
"_node",
"ref",
"_index",
]
This doesn't line up with the examples of Object.keys() I'm seeing on the internet which makes me think this isn't a JSON object like I thought it was. I've tried poking around a good bit with some other stuff but can't figure it out. The problem is, when I use typeof on the object, it simply returns 'object' which is a little too vague for me to take to the google machine.
The following is a representation of my code. Thanks for your help.
import { initializeApp } from 'firebase/app';
import { get, getDatabase, query, ref } from 'firebase/database';
const firebaseConfig = {
databaseURL: '<myURL>',
projectId: '<myID>',
};
const app = initializeApp(firebaseConfig);
export default async function myFunction() {
const db = getDatabase(app);
const readReference = ref(db, '/<I am only reading a piece of the data>')
const existingData = await get(query(readReference))
const dataKeys = Object.keys(existingData)
console.log(dataKeys)
console.log(existingData)
console.log(typeof existingData)
}
What you get back from Firebase is known as a DataSnapshot, and contains the JSON of the location you read, and some more metadata.
If you just want to get the JSON value of the snapshot, use snapshot.val() as shown in the Expo documentation for Storing Data and Receiving Updates.
Something like:
const existingData = await get(query(readReference))
const dataKeys = Object.keys(existingData.val())
console.log(dataKeys)
console.log(existingData.val())
console.log(typeof existingData.val())

Vue Array converted to Proxy object

I'm new to Vue. While making this component I got stuck here.
I'm making an AJAX request to an API that returns an array using this code:
<script>
import axios from 'axios';
export default {
data() {
return {
tickets: [],
};
},
methods: {
getTickets() {
axios.get(url)
.then((response) => {
console.log(response.data) //[{}, {}, {}]
this.tickets = [...response.data]
console.log(this.tickets) //proxy object
})
},
},
created() {
this.getTickets();
}
};
</script>
The problem is, this.tickets gets set to a Proxy object instead of the Array I'm getting from the API.
What am I doing wrong here?
Items in data like your tickets are made into observable objects. This is to allow reactivity (automatically re-rendering the UI and other features). This is expected and the returned object should behave just like the array.
Check out the reactivity docs because you need to interact with arrays in a specific pattern or it will not update on the ui: https://v3.vuejs.org/guide/reactivity-fundamentals.html
If you do not want to have reactivity - maybe you never update tickets on the client and just want to display them - you can use Object.freeze() on response.data;
if you want reactive information use toRaw
https://vuejs.org/api/reactivity-advanced.html#toraw
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
or use unref if you donot want ref wrapper around your info
https://vuejs.org/api/reactivity-utilities.html#unref
You can retrieve the Array response object from the returned Proxy by converting it to a JSON string and back into an Array like so:
console.log(JSON.parse(JSON.stringify(this.tickets)));
You're not doing anything wrong. You're just finding out some of the intricacies of using vue 3.
Mostly you can work with the proxied array-object just like you would with the original. However the docs do state:
The use of Proxy does introduce a new caveat to be aware of: the proxied object is not equal to the original object in terms of identity comparison (===).
Other operations that rely on strict equality comparisons can also be impacted, such as .includes() or .indexOf().
The advice in docs doesn't quite cover these cases yet. I found I could get .includes() to work when checking against Object.values(array). (thanks to #adamStarrh in the comments).
import { isProxy, toRaw } from 'vue';
let rawData = someData;
if (isProxy(someData)){
rawData = toRaw(someData)
}

Firebase.database.ServerValue.TIMESTAMP - Flow error property `TIMESTAMP` (Property not found in object literal)

I keep getting the error
[flow] property TIMESTAMP (Property not found in object literal)
on the following block of code:
firebase
.database()
.ref(`${Config.ROOT}/transactions/${parentId}/`)
.push({
credits,
date: moment().format('YYYY-MM-DDTHH:mm:ssZZ'),
serverTimestamp: firebase.database.ServerValue.TIMESTAMP, // <- here
params: {
value: euros,
},
type: 'topUp',
details: transactionStatus,
})
However, if I hover my mouse over the ServerValue property, even the lint config in my browser confirms that the property TIMESTAMP exists in ServerValue.
What am I missing to make flow accept this block of code?
I might be wrong, but flow seems to be interpreting the ServerValue as a union type. The message says that the ServerValue has two possibilities:
{
TIMESTAMP: any | {}
}
or
{} //Empty
So TIMESTAMP may or may not exist. You probably need to throw an existence check around it. Maybe something like:
const {TIMESTAMP} = firebase.database.ServerValue
if (TIMESTAMP != null) {
firebase
.database()
.ref(`${Config.ROOT}/transactions/${parentId}/`)
.push({
credits,
date: moment().format('YYYY-MM-DDTHH:mm:ssZZ'),
serverTimestamp: TIMESTAMP, // No longer null
params: {
value: euros,
},
type: 'topUp',
details: transactionStatus,
})
} else {
throw new Error("Missing TIMESTAMP");
}
Firebase database TIMESTAMP is documented as a non-null object, and this seems inconsistent with flows understanding, as any would include null (what would the point of any | {} as any would include an object?).
I'd look into where flow is getting its types for firebase, is it using the firebase code in node_modules or is the project using flow-typed, or your own definitions? The definition shown at the top of the screenshot seems correct, but not the one at the bottom. I don't recognise your linter UI to be able to advise on why that might be.
Also, does the code actually work? If necessary, you might be able to use a $FlowFixMe comment to suppress the error if its erroneous.
I'd avoid using a type refinement, as it doesn't document that you are using it to suppress an incorrect flow type annotation.
Here's how you could use $FlowFixMe:
firebase
.database()
.ref(`${Config.ROOT}/transactions/${parentId}/`)
.push({
credits,
date: moment().format('YYYY-MM-DDTHH:mm:ssZZ'),
// $FlowFixMe: TIMESTAMP incorrectly defined as potentially null
serverTimestamp: firebase.database.ServerValue.TIMESTAMP,
params: {
value: euros,
},
type: 'topUp',
details: transactionStatus,
})

Categories

Resources