はじめに
APIレスポンスやサーバーサイドのpropsにDateオブジェクトを渡したら、いつの間にか文字列になっていた——そんな経験はありませんか。JSON.stringifyは標準のJSONに存在しない型を正しく扱えず、DateはISO文字列に、MapやSetは{}や[]に変換され、BigIntに至ってはエラーで落ちてしまいます。
結果として、受け取った側でnew Date(str)のような変換処理をあちこちに書くことになり、型定義とのズレやうっかりミスの温床になりがちです。この記事では、そんなJSONの限界をそのまま乗り越えられるライブラリ「Superjson」を紹介します。
とはいえ、読むより触った方が早いと思います。SuperjsonのserializeとdeserializeでDateやSetがどう扱われるかをブラウザ上で試せるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
Superjsonとは
Superjsonは、JavaScriptの豊富な型をJSONの形を保ったままシリアライズ・デシリアライズできるライブラリです。Blitz.jsチームが開発しており、tRPCの公式トランスフォーマーとしても採用されているため、フルスタックTypeScript構成では特に馴染み深い存在です。
主な特徴
- 豊富な型サポート - Date、Map、Set、RegExp、BigInt、Error、URLなど標準JSONでは表現できない型をそのまま扱えます
- 型安全性 - TypeScriptと組み合わせることで、シリアライズ前後の型がずれにくくなります
- 軽量・依存最小 - ランタイムのオーバーヘッドが小さく、依存パッケージも最小限です
- フレームワーク非依存 - Next.jsやtRPCに限らず、任意のJavaScript/TypeScriptプロジェクトで利用できます
インストール
npm install superjson
yarn add superjson
pnpm add superjson
Superjsonのサンプルを動かす
下のフォームでは、日時とタグを書き換えるとその場でSuperjsonのserialize/deserializeの結果が更新されます。上段がserializeが返すjson(通常のJSON互換オブジェクト)、下段がdeserializeで復元した結果です。日時を変えるとcreatedAtがDateのまま復元されること、タグを書き換えるとtagsがSetのまま復元されることを確認できます。試しに日時欄を未来の日付にしたり、タグ欄を空にしたりして、出力がどう変わるか見てみてください。
要点だけを抜き出すと、次のように書きます。
import superjson from "superjson";
const data = {
createdAt: new Date("2026-01-01T09:00:00.000Z"),
tags: new Set(["typescript", "javascript"]),
};
// json: 通常のJSON互換オブジェクト、meta: 型情報
const { json, meta } = superjson.serialize(data);
// meta付きでdeserializeすると型が復元される
const restored = superjson.deserialize({ json, meta });
restored.createdAt instanceof Date; // true
restored.tags instanceof Set; // true
実際に動かせるサンプルが下です。入力を変えるたびにsuperjson.serializeとsuperjson.deserializeが再実行され、結果がその場で描画されます。
このサンプルのtagsを空文字にすると、Setの中身は空でも型情報(meta)はSetのまま保持され、deserialize後もtags instanceof Setがtrueになり続けることが分かります。逆にdataオブジェクトからsuperjson.serializeを通さずJSON.stringify(data)を直接使うコードに書き換えると、tagsは配列になりSetとしての情報が失われてしまいます。SuperjsonのAPIを経由するかどうかで、この違いが生まれる点がポイントです。
基本的な使い方
JSON.stringify/JSON.parseをそのまま置き換える感覚で使えます。
import superjson from "superjson";
const data = {
createdAt: new Date("2026-01-01T00:00:00.000Z"),
tags: new Set(["typescript", "javascript"]),
meta: new Map([["views", 1200]]),
bigNumber: 9007199254740993n,
};
// シリアライズ
const { json, meta } = superjson.serialize(data);
console.log(json);
// => Dateは文字列に、Setは配列になるが、metaに型情報が保持される
// デシリアライズ
const restored = superjson.deserialize({ json, meta });
console.log(restored.createdAt instanceof Date); // true
console.log(restored.tags instanceof Set); // true
console.log(typeof restored.bigNumber); // "bigint"
文字列としてやり取りしたい場合はstringify/parseを使います。
const str = superjson.stringify(data);
const parsed = superjson.parse<typeof data>(str);
console.log(parsed.createdAt instanceof Date); // true
serializeはjson(通常のJSON互換オブジェクト)とmeta(型情報)を分けて返すため、既存のJSON APIに型情報だけを追加で載せたい場合にも使いやすい設計です。
実践的なユースケース
tRPCのトランスフォーマーとして使う
tRPCではサーバー・クライアント間でDateやMapをそのままやり取りしたい場面が多く、Superjsonが公式に推奨されています。
import { initTRPC } from "@trpc/server";
import superjson from "superjson";
const t = initTRPC.create({
transformer: superjson,
});
export const appRouter = t.router({
getPost: t.procedure.query(() => {
return {
id: "1",
title: "Superjsonのすすめ",
publishedAt: new Date(), // クライアントでもDateオブジェクトとして受け取れる
};
}),
});
APIレスポンスの型復元を共通化する
自前のREST APIでも、レスポンスをSuperjsonでラップしておけば、フロントエンド側での型変換処理を1箇所にまとめられます。
// サーバー側
import superjson from "superjson";
export async function handler() {
const body = superjson.stringify({
id: crypto.randomUUID(),
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
});
return new Response(body, {
headers: { "Content-Type": "application/json" },
});
}
// クライアント側
import superjson from "superjson";
async function fetchSession() {
const res = await fetch("/api/session", { method: "GET" });
const text = await res.text();
return superjson.parse<{ id: string; expiresAt: Date }>(text);
}
このように書いておけば、expiresAtは文字列ではなく最初からDateオブジェクトとして扱えるため、new Date()への変換忘れによるバグを防げます。
まとめ
Superjsonを使うことで、DateやMap、Set、BigIntといったJSONが本来扱えない型を、意識せずにシリアライズ・デシリアライズできるようになります。特にtRPCやNext.jsのようなフルスタックTypeScript構成では、サーバーとクライアントの型の一貫性を保つ強力な武器になります。
「JSONに渡すとDateが文字列になって困る」「型変換処理があちこちに散らばっている」と感じたら、ぜひSuperjsonの導入を検討してみてください。
