Following Snippet
const start = new Date(this.date + 'T' + this.time);
console.log(start); // Thu Sep 12 2019 04:00:00 GMT+0200
const tournament:Tournament = {
start: firebase.firestore.Timestamp.fromDate(start)
}
Passing this tournament Object to a Callable Cloud Function which solo purpose is to save the tournament
passed as a document will save the start
field as a Map with the properties seconds
and miliseconds
instead of a Timestamp in Firestore.
I also tried to just do start: start
but this also does not bring the desired result of a Timestamp being saved in Firestore.
Just for info this is how the Function code stripped down looks like:
firestore.collection('tournaments').doc(slugUrl).set(tournamentData)
(tournamentData is the passed Object from the frontend)
Following Snippet
const start = new Date(this.date + 'T' + this.time);
console.log(start); // Thu Sep 12 2019 04:00:00 GMT+0200
const tournament:Tournament = {
start: firebase.firestore.Timestamp.fromDate(start)
}
Passing this tournament Object to a Callable Cloud Function which solo purpose is to save the tournament
passed as a document will save the start
field as a Map with the properties seconds
and miliseconds
instead of a Timestamp in Firestore.
I also tried to just do start: start
but this also does not bring the desired result of a Timestamp being saved in Firestore.
Just for info this is how the Function code stripped down looks like:
firestore.collection('tournaments').doc(slugUrl).set(tournamentData)
(tournamentData is the passed Object from the frontend)
Share Improve this question asked Sep 12, 2019 at 0:05 niclas_4niclas_4 3,6841 gold badge24 silver badges51 bronze badges2 Answers
Reset to default 6Your Timestamp object must be serialized to JSON before it can be sent to the function. The default serialization breaks the Timestamp into its natural seconds and nanoseconds values the resulting object. All type information is lost.
In your function, you're going to have to read those individual values from the data passed to the function, convert them back into a proper Timestamp object using its two-argument constructor, then write that object to Cloud Firestore. Only then will it be saved as a timestamp type field.
I was able to resolve this by using msgpack instead of JSON for serialization because it has support for conversion of Temporal objects. Converting Firestore Timestamp objects was straightforward because they are based on Temporal Instant.
My use-case was sending Timestamp
objects from one Cloud Function to another Cloud Function via PubSub. The example below is built around that concept but is general enough that it can be extended to other processes. (for example, sending data from a web app to a Cloud Function or vice versa)
First define a custom msgpack ExtensionCodec
that can encode and decode Timestamp
types, then register the extension with the Encoder
and Decoder
objects so that when they're used to serialize data they know how to handle those types.
The snippet below works best when turned into a package so that it can be shared between multiple functions/apps, otherwise it must be copied and pasted into each of the examples that e after it for them to work. I have omitted this snippet from each example below to keep them DRY.
import {
Decoder,
Encoder,
ExtensionCodec,
decodeTimestampToTimeSpec,
encodeTimeSpecToTimestamp,
} from '@msgpack/msgpack'
import { Timestamp } from 'firebase-admin/firestore'
const firestoreTimestampType = 0
const extensionCodec: ExtensionCodec = new ExtensionCodec()
extensionCodec.register({
type: firestoreTimestampType,
encode(input: unknown): Uint8Array | null {
if (input instanceof Timestamp) {
const sec = input.seconds
const nsec = input.nanoseconds
return encodeTimeSpecToTimestamp({ sec, nsec })
} else {
return null
}
},
decode(data: Uint8Array): Timestamp {
const timeSpec = decodeTimestampToTimeSpec(data)
const sec = timeSpec.sec
const nsec = timeSpec.nsec
return new Timestamp(sec, nsec)
},
})
// Always use these instances to encode/decode objects that are or
// contain Timestamp type objects or else they will decode as Map
// objects the same way they do with JSON serialization
export const encoder = new Encoder({ extensionCodec })
export const decoder = new Decoder({ extensionCodec })
Next configure the function that sends data to encode it using the custom encoder:
import { type ClientConfig, PubSub } from '@google-cloud/pubsub'
import { Timestamp } from 'firebase-admin/firestore'
// Replace with your actual projectId and topic name
const pubSub = new PubSub('some-project-id' as ClientConfig).topic('some-topic-name')
interface CustomObject {
timestamp: Timestamp
}
export const sendingCloudFunction = async (): Promise<void> => {
const seconds = Math.floor(Date.now() / 1000)
const nanoseconds = 0
const timestamp: Timestamp = new Timestamp(seconds, nanoseconds)
const customObject: CustomObject = { timestamp }
// Encode the custom object with msgpack
const encodedObject: Uint8Array = encoder.encode(customObject)
// Pass this data on to the next function by publishing to PubSub
const data: Buffer = Buffer.from(
encodedObject.buffer,
encodedObject.byteOffset,
encodedObject.byteLength
)
await pubSub.publishMessage({ data })
}
Finally, configure the function that receives the data to decode it using the custom decoder:
import { type PubsubMessage } from '@google-cloud/pubsub/build/src/publisher'
import { Timestamp } from 'firebase-admin/firestore'
interface CustomObject {
timestamp: Timestamp
}
export const receivingCloudFunction = async (
message: PubsubMessage,
): Promise<void> => {
if (message.data === null || message.data === undefined) {
throw new Error('PubSub message must be defined')
}
// Decode the PubSub data back into a msgpack-encoded object
const encodedObject: Buffer = Buffer.from(message.data.toString(), 'base64')
// Decode the msgpack-encoded object back into CustomObject
const decodedObject = decoder.decode(encodedObject) as CustomObject
if (decodedObject.timestamp instanceof Timestamp) {
console.log(`Successfully decoded ${decodedObject.timestamp.toDate().toString()} as type Timestamp`)
}
}
When receivingCloudFunction
is invoked it will log something similar to:
Successfully decoded Tue Mar 28 2023 12:02:11 GMT-0700 (Pacific Daylight Time) as type Timestamp