导出导入Firestore Timestamp数据后无法调用toDate()等方法的问题咨询
嗨,我完全懂你遇到的困扰!这个问题的核心其实是JSON序列化/反序列化的特性和Firestore Timestamp类的本质导致的,咱们来一步步理清楚:
为什么导入后的Timestamp没法调用方法?
Firestore里的Timestamp是一个自定义类的实例,它不仅包含seconds和nanoseconds这两个原始属性,还自带toDate()、toMillis()这些方法。但JSON的规则很简单:它只能序列化对象的可枚举属性,类的方法是不会被转换成JSON字符串的。
所以当你用JSON.stringify(todos)导出数据时,Timestamp实例会被转换成一个只包含seconds和nanoseconds的普通对象;等到你用JSON.parse解析回来时,得到的也只是这个普通对象,而不是Firestore Timestamp类的实例——自然就没法调用那些专属方法了。
解决办法:序列化时标记类型,反序列化时还原实例
我们需要在导出和导入的过程中,手动处理Timestamp对象,让它能在JSON中保留“可还原”的标记:
1. 修改导出函数,给Timestamp打标记
在JSON.stringify时传入一个replacer函数,识别Timestamp并添加类型标记:
const dataStr = JSON.stringify(todos, (key, value) => { // 判断当前值是否是Firestore Timestamp实例 if (value?.constructor?.name === "Timestamp" || (value?.seconds !== undefined && value?.nanoseconds !== undefined)) { return { _type: "FirestoreTimestamp", // 自定义类型标记 seconds: value.seconds, nanoseconds: value.nanoseconds }; } return value; }); // 后面的导出逻辑不变 let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); let fileName = 'data.json'; let linkElement = document.createElement('a') as HTMLAnchorElement; linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', fileName); linkElement.click();
2. 修改导入函数,还原Timestamp实例
在JSON.parse时传入一个reviver函数,识别标记并还原成Firestore Timestamp实例:
const uploadFile = fileInput.files[0]; const fileReader = new FileReader(); fileReader.onload = async (e) => { const dataStr = e.target?.result as string; // 解析时还原Timestamp const newDatas = JSON.parse(dataStr, (key, value) => { if (value?._type === "FirestoreTimestamp") { // 根据你的Firestore SDK类型选择还原方式: // 如果你用的是命名空间SDK(firebase.firestore): return new firebase.firestore.Timestamp(value.seconds, value.nanoseconds); // 如果你用的是模块化SDK: // import { Timestamp } from "firebase/firestore"; // return Timestamp.fromObject({ seconds: value.seconds, nanoseconds: value.nanoseconds }); } return value; }) as todosProps[]; console.log(newDatas); settodos([...todos, ...newDatas]); try { // 现在newDatas里的Timestamp是真实实例,写入Firestore也会正常识别 await Promise.all(newDatas.map(async (d) => { await setDoc(doc(collectionRef, d.id.toString()), { ...d }); })); console.log('finish import'); } catch (error) { console.error(error); } };
额外提示
这里我还修改了导入时的map逻辑——原来的newDatas.map(async (d) => {})会返回一堆Promise,但你没有等待它们全部完成,可能会出现“finish import”打印了但数据还没写完的情况,换成Promise.all就能保证所有写入操作完成后再打印提示。
这样处理之后,你导入的数据里的Timestamp就和原始数据的Timestamp实例完全一样,不仅能正常调用toDate()、toMillis(),排序逻辑也能恢复正常啦!
备注:内容来源于stack exchange,提问作者ThanHtutZaw

