Firestore.Timestamp に触れたくないあなたのための黒魔術


Firestore.Timestamp って、いらなくね?

ちゃんと使いこなせばメリットもあるんだろうなぁと思いつつ・・・
少なくとも小規模なプロジェクトでは、Timestamp型が存在するせいで

  • Firestore から読んだ、時刻フィールドを toDate() で Date型にもどす
  • Document の interface と、 REST をまたぐ interface を分ける

みたいな対応が必要になるわけです。いちいちめんどいんすこれ。
Date 型で十分な状況もあるんです。

問題の例

interface User {
  name: string;
  updatedAt: Date;
}

const write: User = {
  name: "taro",
  updatedAt: Date.now(),
};
await firestore
  .collection("users")
  .doc(write.name)
  .set(write);

const read: User = 
  await firestore
    .collection("users")
    .doc(write.name)
    .get()
    .then(dss=>dss.data());

console.log(read); // updatedAt が Timestamp クラスになってるよ!

魔術の源: Timestamp を Date へ再帰的に変換するヘルパーメソッド

function timestampToDateRecursively(value: any): any{
  if (value == null) {
    return value;
  } else if (value.constructor === Firestore.Timestamp) {
    return value.toDate();
  } else if (Array.isArray(value)) {
    return value.map(timestampToDateRecursively);
  } else if (value.constructor === Object) {
    const converted: any = {};
    for (const key in value) {
      converted[key] = timestampToDateRecursively(value[key]);
    }
    return converted;
  } else {
    return value;
  }
}

白魔術: いちいちヘルパーメソッドをかませる

  • どこで何が起きてるかわすりやすい。安全。
  • いちいち忘れたりする。めんどい。
const read: User = 
  await firestore
    .collection("users")
    .doc(write.name)
    .get()
    .then(dss=>timestampToDateRecursively(dss.data())); // 毎回書く必要あり 

console.log(read); // updatedAt がちゃんと Date になってるね

黒魔術: DocumentSnapshot.data() をオーバーライドする

  • 意識しなくても勝手に変換されてる。便利。
  • 知らないと、Firestoreの仕様の誤解や混乱のもとになる。危険。
/**
 * DocumentSnapShot.data() で返すすべてのTimestamp型をあらかじめDate型へ変換するよう
 * プロトタイプをオーバーライドします
 */
function wrapDocumentSnapshotData() {
  console.log(`Wrapping DocumentSnapshot.data()`);
  const origin = Firestore.DocumentSnapshot.prototype.data;
  Firestore.DocumentSnapshot.prototype.data = function () {
    const data = origin.bind(this)();
    const converted = timestampToDateRecursively(data); // ここ!
    return converted;
  };
}

wrapDocumentSnapshotData();

const read: User = 
  await firestore
    .collection("users")
    .doc(write.name)
    .get()
    .then(dss=>dss.data()); // いちいち意識しなくていい

console.log(read); // updatedAt がちゃんと Date になってるね