Firestore のデータ型の1つに Timestamp 型があります。これは Firestore 独自の型です。

Timestamp が持っている toDate() メソッドを呼び出すと、JavaScript の Date 型に変換することができます。クライアント側でデータを取得した後、データを操作する際にまず toDate() を呼び出すというのはよくある動きだと思います。

ドキュメントの Timestamp をクライアント側で一括して Date 型として扱って良い場合、取得したドキュメントのすべての Timestamp 型フィールドに対して toDate() を実行する処理があると便利です。

これを実現する関数を作成しましたのでご紹介します。

TL;DR

以下がコードです。ポイントを後ほど補足します。

import firebase from 'firebase/app';

const { Timestamp } = firebase.firestore;

type Datefied<T> = {
  [K in keyof T]: T[K] extends typeof Timestamp
    ? Date
    : T[K] extends typeof Timestamp[]
    ? Date[]
    : T[K] extends unknown[] | Record<string, unknown>
    ? Datefied<T[K]>
    : T[K];
};

const datefy = <T extends Record<string, unknown>>(document: T) => {
  Object.entries(document).forEach(([key, value]) => {
    if (typeof value !== 'object' || value === null) return;
    if (value instanceof Timestamp) {
      // @ts-ignore
      document[key] = value.toDate();
      return;
    }
    // @ts-ignore
    document[key] = datefy(value);
  });
  return document as Datefied<T>;
};

使用例

Firestore から取得したデータをそのまま datefy() に渡すだけです。

datefy() は、データ内に存在する Timestamp 値のフィールドすべてを toDate() 後の値に変換した上で、Date 型を当てて返すようになっています。

const snapshot = await db.collection('users').doc('***').get();
const data = snapshot.data();

console.log(data);
// 以下の <Timestamp> には実際の Timestamp 値が入っているとします。
// {
//   name: 'Alice',
//   birthday: <Timestamp>,
//   friends: [
//     { name: bob, meetAt: <Timestamp> },
//     { name: charlie, meetAt: <Timestamp> },
//   ],
//   favoriteDates: [<Timestamp>, <Timestamp>, <Timestamp>]
// }

type Timestamp = firebase.firestore.Timestamp;

type Data = {
  name: string;
  birthday: Timestamp;
  friends: { name: string; meetAt: Timestamp }[];
  favoriteDates: Timestamp[];
};

const datefiedData = datefy<Data>(data);

console.log(datefiedData);
// 以下の <Date> には Timestamp を toDate() した値が入っています。
// {
//   name: 'Alice',
//   birthday: <Date>,
//   friends: [
//     { name: bob, meetAt: <Date> },
//     { name: charlie, meetAt: <Date> },
//   ],
//   favoriteDates: [<Date>, <Date>, <Date>]
// }

// datefiedData の型は以下となっています。
// {
//   name: string;
//   birthday: Date;
//   friends: { name: string; meetAt: Date }[];
//   favoriteDates: Date[];
// };

解説

datefy() の内部実装についてです。引数として受け付けたオブジェクトの値を確認し、Timestamp であれば toDate() を実行します。値がオブジェクトや配列であれば、さらにその中身に対して再帰的に同様の処理を実行します。

型を当てている Datefied<T> についても同様です。型が Timestamp であれば Date に変更、配列やオブジェクトであればその中身に対して再帰的に同様の処理をします。それ以外の型であれば変換せずにそのままとします。

以上

この記事がお役に立てば幸いです!