JSONで使えるデータ型は、文字列・数値・真偽値・null・オブジェクト・配列の6つだけです。日付型も整数型もundefinedもありません。そのため、JSON.stringify()を通すとDateは文字列に、MapやSetは空のオブジェクトに変わり、エラーも出ません。この記事では、6つの型の一覧と書き方、JSONに無い型が変換でどうなるか、TypeScriptでの型定義と実行時の検証を順に扱います。掲載したコードと実行結果は、2026年10月21日にNode.js 26.5.0・TypeScript 7.0.2・Zod 4.6.7で動かして確認したものです。
まとめ:JSONの6つの型と、型定義・実行時検証の分担
- 使える型は6つ:string・number・boolean・null・object・array。numberは整数と小数を区別しません。
- 無い型は黙って変わる:オブジェクト内の
undefined・関数はキーごと消え、配列内ではnullになります。NaNとInfinityはnull、Map・Setは{}になります。BigIntと循環参照はTypeErrorを投げます。 - TypeScriptの型定義だけでは守れない:
JSON.parse()の戻り値はanyです。unknownで受けて、ZodやJSON Schemaで検証してから使います。 - .jsonのimport:
moduleがnodenextで、ESMとして扱われるファイルからJSONをimportするときはwith { type: "json" }が必須です(無いとTS1543)。 - 253を超える整数:
JSON.parse()では精度が保証されず、値によっては下位の桁が変わります。JSON.rawJSON()とreviverのcontext.sourceで避けられますが、TypeScript 7.0.2には型が無いため、自分で宣言を足します。 - 「JSON型」には別の意味もある:MySQLやPostgreSQLのJSONカラム型です。保存形式によって重複キーや小数の表記の扱いが変わり、MySQLは数値をINTEGER・DOUBLEなどに細かく分類します。
JSONで使える6つのデータ型の一覧と書き方
JSONの型は、RFC 8259とECMA-404で定義されています。プリミティブが4つ(文字列・数値・真偽値・null)、構造が2つ(オブジェクト・配列)です。
| 型 | 書き方 | 例 | 書けない例 |
|---|---|---|---|
| 文字列(string) | ダブルクォートで囲む | "山田" |
'山田' |
| 数値(number) | 10進数・指数表記 | 12800 -1.5 1e3 |
01 .5 NaN |
| 真偽値(boolean) | 小文字のtrue/false | true |
True |
| null | 小文字のnull | null |
undefined |
| オブジェクト(object) | 波かっこ内にキーと値 | {"id": 1} |
{id: 1} |
| 配列(array) | 角かっこ内に値を並べる | [1, "a", null] |
[1, 2,] |
6つの型を1つのデータにまとめると、次のようになります。オブジェクトの中にオブジェクトや配列を入れ子にできます。文法上の深さの上限はありませんが、RFC 8259の第9節は、パーサが入れ子の深さに制限を設けることを認めています。
{
"userId": 1024,
"name": "山田太郎",
"isActive": true,
"lastLogin": null,
"profile": {
"department": "開発部",
"skills": ["TypeScript", "Go"]
},
"orders": [
{ "orderId": "A-001", "amount": 12800 },
{ "orderId": "A-002", "amount": 4300 }
]
}
数値型の整数・小数の区別と精度の範囲
JSONの数値は1つの型だけで、1と1.0はどちらも同じnumberです。精度の規定はなく、RFC 8259の第6節は、IEEE 754の倍精度(binary64)を超える精度を期待しない実装なら相互運用できるとしています。整数が正確に一致する範囲は-(2**53)+1から(2**53)-1、つまり±9,007,199,254,740,991までです。I-JSON(RFC 7493)も、これを超える整数を受け手が正確に扱うことは期待できないとしています。
この範囲を超えると、JavaScriptで読んだときに別の数になる場合があります。Node.js 26.5.0では9007199254740993が9007199254740992に変わり、9007199254740994はそのまま読めました。範囲外では、正確に表せる値と表せない値が混在するということです。また、JSON.parse('1e400')はエラーにならずInfinityを返し、もう一度JSON.stringify()するとnullになります。注文IDや外部サービスのIDのように桁数が大きい値は、送る側で"12345678901234567890"のように文字列にしておくのが確実です。受け取った数値が整数であることを保証したいときは、Number.isInteger()やスキーマ(Zodのz.number().int()、JSON Schemaの"type": "integer")で検証します。
真偽値とnullの書き方:小文字だけが有効
真偽値はtrueとfalse、nullはnullで、すべて小文字のキーワードです。Trueと書くとJSON.parse()は「Unexpected token ‘T’」で失敗します。"true"のようにクォートで囲んだ値は文字列なので、受け取り側でif (value)と判定すると"false"も真になります。
「値が無い」の表し方は2通りあります。キーを残してnullを入れるか、キーごと省くかです。APIの仕様では、どちらを使うかを項目ごとに決めておきます。TypeScriptでは前者を| null、後者を?で型定義します(後述)。
オブジェクトと配列の違い:キーによる参照と順序付きの並び
オブジェクトは「キー(プロパティ名)と値」の組の集まりで、RFC 8259では順序の無い集合と定義されています。キーは必ずダブルクォートで囲んだ文字列で、数値や真偽値をキーにはできません。配列は値を順番どおりに並べたもので、要素の型が混ざっていても構いません。
使い分けは、1件のレコードならオブジェクト、同じ形のデータが繰り返すならオブジェクトの配列です。上の例のordersがこの形です。同じオブジェクトに同じキーを2回書いた場合の扱いは仕様で決まっておらず、RFC 8259はキーを一意にすべき(SHOULD)としています。JavaScriptのJSON.parse('{"a":1,"a":2}')は後の値を採用して{ a: 2 }を返します。なお、RFC 8259ではルートに配列や文字列を置くこともでき、[1, 2]や"text"も単独で正しいJSONです。
JSONに無い型の変換結果:Node.js 26で実測した一覧
JavaScriptの値をJSONにするとき、6つの型に無い値はJSON.stringify()が置き換えるか、捨てます。置き換え方は「その値がどこにあるか」で変わります。Node.js 26.5.0で、単独・オブジェクトの値・配列の要素の3通りを実行した結果が次の表です。
| 値 | 単独でstringify | オブジェクトの値 | 配列の要素 |
|---|---|---|---|
undefined |
undefined(文字列でない) |
キーごと消える | null |
| 関数・Symbol | undefined(文字列でない) |
キーごと消える | null |
NaN・Infinity |
"null" |
null |
null |
-0 |
"0" |
0 |
0 |
| Date | ISO 8601のUTC文字列 | 同左 | 同左 |
| 無効なDate | "null" |
null |
null |
| Map・Set・正規表現 | "{}" |
{} |
{} |
| BigInt | TypeError | TypeError | TypeError |
| 循環参照 | TypeError | TypeError | TypeError |
最も気づきにくいのはMapとSetです。中身があっても{}になり、例外もログも出ません。DateはtoJSON()でUTCに変換されるため、日本時間の2026-10-21T09:00:00+09:00は"2026-10-21T00:00:00.000Z"として出力され、JSON.parse()で戻しても文字列のままです。new Date(NaN)のような無効な日時は、文字列にならずnullになります。BigIntは「TypeError: Do not know how to serialize a BigInt」、自分自身を参照するオブジェクトは「TypeError: Converting circular structure to JSON」で止まるので、少なくとも気づけます。
対策は、送る前に6つの型へ自分で変換しておくことです。
const payload = {
createdAt: createdAt.toISOString(), // Date → ISO 8601文字列
tags: [...tagSet], // Set → 配列
counts: Object.fromEntries(countMap), // Map → オブジェクト(キーが文字列の場合)
orderId: orderId.toString(), // BigInt → 文字列
};
const body = JSON.stringify(payload);
JSONの書き方と構文エラー:エラーメッセージと原因の対応表
JSONの構文はJavaScriptのオブジェクトリテラルより厳格です。よくある誤りと、Node.js 26.5.0のJSON.parse()が出すメッセージ(位置の部分は省略)を並べます。
| 誤り | 例 | JSON.parseのエラー |
|---|---|---|
| シングルクォート | {'a': 1} |
Expected property name or ‘}’ |
| キーのクォート省略 | {a: 1} |
Expected property name or ‘}’ |
| 末尾カンマ | {"a": 1,} |
Expected double-quoted property name |
| コメント | {"a": 1 // メモ} |
Expected ‘,’ or ‘}’ after property value |
| 先頭の0 | {"a": 01} |
Unexpected number |
| 文字列内の生の改行 | 値の途中で改行 | Bad control character in string literal |
| 大文字・NaN | {"a": True} |
Unexpected token ‘T’ |
| BOM付きファイル | 先頭にU+FEFF | Unexpected token(不可視の文字) |
シングルクォートとキーのクォート省略は同じメッセージになるため、メッセージの後ろに付く「line 1 column 2」のような位置で該当箇所を見ます。文字列に含めるダブルクォートは\"、バックスラッシュは\\、改行は\n、タブは\tとエスケープします。Windowsのパスは"C:\\Users\\app"のようにバックスラッシュを二重にします。BOMは、RFC 8259が「ネットワークで送信するJSONの先頭に付けてはならない(MUST NOT)」と定めています。ただ、エディタの設定によってはファイル保存時に付くことがあり、そのまま読み込むとJSON.parse()は先頭の不可視文字で失敗します。
JSONにコメントは書けません。設定ファイルでコメントを残したい場合のJSONC・JSON5への切り替えはJSONにコメントは書ける?コメントアウトできない理由とJSONC・JSON5の書き方・言語別の読み込み実装で扱っています。
TypeScriptでのJSONの型定義:型の宣言と実行時の検証の違い
TypeScriptでJSONの型定義を書くときの要点は、型の宣言(コンパイル時)と値の検証(実行時)が別物だということです。interfaceは実行時には消えるため、届いたJSONが宣言どおりかは確かめてくれません。型注釈と型推論の基本はTypeScriptとは?型・型推論・型注釈の書き分けとJavaScriptとの違いを先に読むと、以降のコードが追いやすくなります。
interfaceで構造を宣言する:「?」と「| null」の違い
JSONの6つの型はTypeScriptのstring・number・boolean・null・オブジェクト型・配列型にそのまま対応します。先ほどのJSONをinterfaceで書くと次のとおりです。
interface Order {
orderId: string;
amount: number;
}
interface User {
userId: number;
name: string;
isActive: boolean;
lastLogin: string | null; // キーは必ずあり、値が null になりうる
nickname?: string; // キー自体が無いことがある
profile: {
department: string;
skills: string[];
};
orders: Order[];
}
lastLoginのようにキーは必ず来て値がnullになりうる項目は| null、キーごと来ないことがある項目は?で表します。2つを混同すると、nullチェックかundefinedチェックのどちらかが漏れます。JSON上はどちらもただの文字列であるユーザーIDと注文IDを取り違えないようにしたい場合は、TypeScriptのBranded Types(ブランド型)とは|実装と型安全の設計指針の手法が使えます。
JSON.parse()の戻り値のanyと、unknownでの受け取り
TypeScriptの標準ライブラリ(lib.es5.d.ts)は、JSON.parse()の戻り値をanyと宣言しています。そのため、次のコードは型検査を通りますが、APIが何を返しても素通りします。
const user: User = JSON.parse(text);
// 型エラーにならない。any は何にでも代入できる
// text が {"foo": 1} でも通り、実行時に user.name.toUpperCase() で TypeError になる
受け口をunknownにすると、検証を通すまでプロパティに触れなくなります。TypeScript 7.0.2では、unknownのままparsed.nameを読むとTS18046のエラーになります。
const parsed: unknown = JSON.parse(text);
// parsed.name はここではコンパイルエラー。検証を通してから使う
fetch()のres.json()も戻り値はPromise<any>なので、扱いは同じです。ステータス204のようにボディが空の応答でres.json()を呼ぶと「SyntaxError: Unexpected end of JSON input」になるため、先にステータスで分岐します。
Zodによる実行時の検証と、z.inferでの型定義の一本化
interfaceとは別に検証コードを書くと、片方だけ直して食い違う事故が起きます。Zodではスキーマを1つ書けば、検証と型定義の両方がそこから得られます。
import { z } from "zod";
const OrderSchema = z.object({
orderId: z.string(),
amount: z.number().int().nonnegative(),
});
const UserSchema = z.object({
userId: z.number().int(),
name: z.string(),
isActive: z.boolean(),
lastLogin: z.iso.datetime().nullable(),
nickname: z.string().optional(),
profile: z.object({
department: z.string(),
skills: z.array(z.string()),
}),
orders: z.array(OrderSchema),
});
type User = z.infer<typeof UserSchema>; // スキーマから型を生成(interfaceは不要)
function loadUser(text: string): User {
const result = UserSchema.safeParse(JSON.parse(text));
if (!result.success) {
console.error(z.prettifyError(result.error));
throw new Error("APIレスポンスの形式が想定と違います");
}
return result.data; // ここでは User 型が保証されている
}
userIdが文字列の"1024"、lastLoginが"2026/10/21"、amountが1.5のJSONを渡すと、z.prettifyError()は次のように違反箇所ごとに出力します(Zod 4.6.7)。
✖ Invalid input: expected number, received string
→ at userId
✖ Invalid ISO datetime
→ at lastLogin
✖ Invalid input: expected int, received number
→ at orders[0].amount
Zodの導入とバージョンごとの変更点はZodの使い方入門|npmでのインストールとz.infer・safeParse・Zod 4.6の変更点、バンドルサイズを抑えたい場合の代替はValibotとは?Zodとの違いとpipe構文の使い方をv1系の実コードで解説にまとめています。
.jsonファイルをimportして型を推論させる設定
設定ファイルのような手元の.jsonは、importすれば中身から型が推論されます。moduleがnodenextで、importする側のファイルがESMとして扱われる(package.jsonが"type": "module"、または拡張子が.mts)ときは、import属性with { type: "json" }が必要です。これはTypeScript 5.7から検査されるようになり、属性を省くとTS1543になります。逆に、CommonJSとして扱われるファイル("type": "commonjs")に属性を書くと、TS2856「Import attributes are not allowed on statements that compile to CommonJS ‘require’ calls」になります(TypeScript 5.9.3・7.0.2で確認)。
// tsconfig.json の compilerOptions は "module": "nodenext"、package.json は "type": "module"
import config from "./config.json" with { type: "json" };
const retry: number = config.retry; // number として推論される
const base: string = config.apiBase; // string として推論される
resolveJsonModuleは、moduleがnodenext、またはmoduleResolutionがbundlerのときは書かなくても有効で、TypeScript 5.9.3と7.0.2のどちらでも.jsonを読めました。明示的にfalseにするとTS2732になります。古い記事にある"esModuleInterop": trueは、TypeScript 7.0ではfalseを指定できなくなった(TS5108「has been removed」)ため、書く必要がありません。
サンプルJSONやJSON Schemaからの型定義の自動生成
手書きの代わりに、ツールで型を生成する方法もあります。サンプルのJSONから生成するのがquicktype、JSON Schemaから生成するのがjson-schema-to-typescriptです。
# サンプルJSONから生成(出力先の拡張子 .ts から言語を判定)
npx quicktype user.json -o user.ts --just-types
# JSON Schemaから生成(json2ts は json-schema-to-typescript のCLI)
npx --package=json-schema-to-typescript json2ts -i user.schema.json -o user.d.ts
同じユーザーのデータで比べると、差が出たのはlastLoginでした。サンプルでnullだった項目を、quicktype 26.0.0はlastLogin: nullと推論し、文字列が来ると型が合いません。json-schema-to-typescript 16.0.0はスキーマの"type": ["string", "null"]からlastLogin: string | nullを出しました。サンプルからの生成は、出てきた型を必ず読み直す前提の叩き台として使い、本番の型の正本にはスキーマかZodを置きます。
JSON Schemaによる言語をまたいだ型定義の共有
JSON SchemaはJSONの型定義をJSONで書く仕様で、TypeScript以外の言語やAPI仕様書(OpenAPI)とも共有できます。公式サイトが現行版として案内しているのはDraft 2020-12です。typeキーワードに書ける値はstring・number・integer・boolean・object・array・nullの7つで、JSON本体には無いintegerを区別できます。書き方の全体はJSON Schemaとは?JSONの構造を検証する書き方とDraft 2020-12の実装で解説しているので、ここでは型定義として使うときにつまずく2点に絞ります。
ZodからJSON Schemaへの出力と、余分なキーの扱いの差
Zod 4はz.toJSONSchema()でスキーマを出力でき、既定はDraft 2020-12です(targetでdraft-07・draft-04・openapi-3.0も選べます)。注意したいのは、z.object()は余分なキーを検証で落とさず黙って取り除くのに、出力されたJSON Schemaには"additionalProperties": falseが付くことです。同じデータでも、Zodでは通り、Ajvでは「must NOT have additional properties」で弾かれます。
import { z } from "zod";
const S = z.object({ a: z.string() });
z.toJSONSchema(S);
// {..., "required": ["a"], "additionalProperties": false}
z.toJSONSchema(S, { io: "input" });
// {..., "required": ["a"]} ← additionalProperties が付かない
出力したスキーマを他チームの受信側の検証に渡すなら{ io: "input" }を付け、余分なキーを拒否したいならZod側もz.strictObject()にそろえます。z.date()とz.bigint()はJSON Schemaで表せないため、toJSONSchema()がそれぞれ「Date cannot be represented in JSON Schema」「BigInt cannot be represented in JSON Schema」のエラーを投げます。
AjvをESMから使うときのimportの書き方
Node.jsのESM("type": "module")やmodule: nodenextでは、import Ajv2020 from "ajv/dist/2020"という古い書き方は動きません。拡張子が無いためNode.jsはERR_MODULE_NOT_FOUND、TypeScriptはTS2307を出します。ajv-formatsは、実行時には関数として呼べるものの、型検査ではTS2349「This expression is not callable」になります。Ajv 8.20.0とajv-formats 3.0.1で動いた書き方は次のとおりです。
import { Ajv2020 } from "ajv/dist/2020.js";
import addFormatsModule from "ajv-formats";
import schema from "./user.schema.json" with { type: "json" };
// module: nodenext の型検査では default import が関数として扱われないため .default を参照する
//(Node.js 26.5.0 の実行時は addFormatsModule と .default が同じ関数)
const addFormats = addFormatsModule.default;
const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv); // "format": "date-time" などを検証させる
const validate = ajv.compile(schema);
const data: unknown = JSON.parse(text);
if (!validate(data)) {
for (const e of validate.errors ?? []) {
console.error(e.instancePath || "(root)", e.message);
}
throw new Error("JSONがスキーマに合いません");
}
JSON.parse()とJSON.stringify()で型を戻す・隠す:reviverとreplacer
JSON.stringify()の第2引数(replacer)は出力するキーを選び、JSON.parse()の第2引数(reviver)は読み込んだ値を変換します。どちらも、JSONの6つの型とアプリケーションの型を行き来させるための入口です。
const user = { id: 1, name: "山田", password: "secret", token: "abc123" };
// 配列のreplacer:指定したキーだけを出力する(第3引数はインデント幅)
JSON.stringify(user, ["id", "name"], 2);
// 関数のreplacer:undefined を返したキーは出力から消える
JSON.stringify(user, (key, value) =>
key === "password" || key === "token" ? undefined : value
);
// {"id":1,"name":"山田"}
ログやエラーレポートにオブジェクトを丸ごと出すときは、配列のreplacerで出すキーを列挙しておくと、後から増えた機密項目が漏れません。逆方向の、ISO 8601文字列をDateに戻す処理はreviverで書きます。
const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
const data = JSON.parse(text, (key, value) =>
typeof value === "string" && ISO.test(value) ? new Date(value) : value
);
// createdAt などのUTC文字列が Date として復元される
この正規表現は、日付らしい文字列をすべてDateに変えます。日付が入るキーが決まっているなら、key === "createdAt"のようにキーで絞る方が誤変換が起きません。
253を超える整数の精度落ちを防ぐ:JSON.rawJSON()とcontext.source
前述のとおり、JavaScriptのnumberで正確さが保証される整数は±9,007,199,254,740,991までです。この範囲を超えると正確に表せない値が出てきて、JSON.parse()はそうした値をエラーを出さずに最も近いnumberへ丸めます。
JSON.parse('{"id": 12345678901234567890}').id;
// 12345678901234567000 ← 下位の桁が変わっている
ECMAScript 2026で標準化された2つの機能で、これを防げます。reviverの第3引数contextのsourceには丸める前の元のテキストが入り、JSON.rawJSON()は数値をクォートなしのまま出力させます。MDNはこの機能を2025年3月からBaseline(主要ブラウザで利用可)としています。ただし、TypeScript 7.0.2の標準ライブラリには、libをesnextにしてもこの2つの型がありません。そのまま書くとTS2339(rawJSONが無い)とTS2345(reviverの引数が多い)で落ちるため、宣言ファイルを足します。
// json-raw.d.ts:TypeScript 7.0.2 の lib に無いため自前で宣言する
interface RawJSON { readonly rawJSON: string }
interface JSON {
rawJSON(text: string): RawJSON;
isRawJSON(value: unknown): value is RawJSON;
parse(
text: string,
reviver: (this: unknown, key: string, value: unknown, context: { source?: string }) => unknown
): any;
}
// パース:元のテキストを BigInt に渡して桁を保つ
const parsed = JSON.parse('{"id": 12345678901234567890}', (key, value, context) => {
if (key === "id" && context.source !== undefined) {
return BigInt(context.source);
}
return value;
});
console.log(parsed.id); // 12345678901234567890n
// 出力:rawJSON でクォートなしの数値として書き戻す
JSON.stringify({ id: JSON.rawJSON(parsed.id.toString()) });
// {"id":12345678901234567890}
context.sourceが入るのは数値・文字列などのプリミティブだけで、オブジェクトや配列ではundefinedです。JSON.rawJSON('{"a":1}')のようにオブジェクトを渡すとSyntaxErrorになります。APIの仕様を自分で決められるなら、大きなIDは最初から文字列で送る方が、受け手の言語やライブラリを問わず安全です。
データベースの「JSON型」:MySQLとPostgreSQLのカラム型
MySQLのリファレンスは、JSON文書を格納するカラムの型を「JSONデータ型」と呼んでいます。これは本記事の6つのデータ型とは別の、データベースのカラム型の話です。MySQLのJSON型と、PostgreSQLのjson型・jsonb型があり、格納時に検証されるため、壊れたJSONは入りません(MySQL 8.4.8では'{bad}'の挿入がERROR 3140で拒否されました)。
違いが出るのは、6つの型より細かい扱いです。PostgreSQL 18.6とMySQL 8.4.8で同じ値を入れた結果を並べます。
| 入れた値 | PostgreSQL json | PostgreSQL jsonb | MySQL JSON |
|---|---|---|---|
{"b":1, "a":2, "a":3} |
入力のまま保持 | {"a": 3, "b": 1} |
{"a": 3, "b": 1} |
{"x":1.50} |
入力のまま保持 | {"x": 1.50} |
{"x": 1.5} |
12345678901234567890 |
入力のまま保持 | 桁を保持 | 桁を保持 |
99999999999999999999 |
入力のまま保持 | 桁を保持 | 1e20(DOUBLE) |
| 数値の型判定 | – | number |
INTEGER/DOUBLE |
PostgreSQLのjsonは入力テキストのコピーを保存し、関数を実行するたびに解析し直します。jsonbは分解したバイナリ形式で保存するので、重複キーは後の値だけ残り、キーの順序は変わりますが、検索が速くインデックスも張れます(PostgreSQL公式 8.14)。MySQLも内部のバイナリ形式で保存し、JSON_TYPE()は1をINTEGER、1.5をDOUBLEと区別して返します。1文書の大きさはmax_allowed_packet(MySQL 8.4の既定は67,108,864バイト=64MiB)で制限されます(MySQL 8.4 リファレンス)。
-- MySQL 8.4: JSONカラムから値を取り出す(->> は引用符を外した文字列)
CREATE TABLE events (id INT PRIMARY KEY, payload JSON);
SELECT payload->'$.userId' AS user_id, payload->>'$.type' AS type FROM events;
-- PostgreSQL 18: jsonb と GIN インデックスで包含検索する
CREATE TABLE events (id INT PRIMARY KEY, payload JSONB);
CREATE INDEX idx_events_payload ON events USING GIN (payload);
SELECT payload->>'userId' AS user_id FROM events WHERE payload @> '{"type":"login"}';
MySQLのJSON_TYPE()は12345678901234567890をUNSIGNED INTEGERと判定して桁を保ちましたが、符号なし64ビット整数の上限(18,446,744,073,709,551,615)を超える99999999999999999999はDOUBLEになり、1e20に変わりました。PostgreSQLのjsonbは数値をnumeric型で持つため、どちらも桁を保ちます。DBで桁を保てても、アプリケーションがJavaScriptで通常のnumberとして読めば前章の丸めが起きるので、文字列やBigIntで受け取る処理が別に必要です。jsonbのインデックス設計や、正規化した列に切り出す判断はPostgreSQL JSONBの使い方|json型との格納差・GIN索引と正規化列の設計境界で詳しく扱っています。
JSONの型定義の選び方:interface・Zod・JSON Schema・自動生成
| 方法 | 実行時の検証 | 向く場面 |
|---|---|---|
| interface・type | なし | 自分で作って自分で読むデータ |
| Zod(z.infer) | あり | TypeScriptだけで完結する受信処理 |
| JSON Schema+Ajv | あり | 他言語・OpenAPIと型を共有する |
| quicktypeで生成 | なし | 初めて見るAPIの叩き台 |
外部APIのレスポンス、ユーザーがアップロードしたファイル、他チームのサービスから届くメッセージには、interfaceだけで済ませない方が安全です。これらは自分のコードの外で形が変わるため、TypeScriptのコンパイルが通っても実行時に壊れます。検証を入れる場所は、JSONを受け取った直後の1か所にまとめます。そこから先のコードは検証済みの型だけを扱えます。
TypeScriptだけのチームならZodを正本にし、必要なときにz.toJSONSchema()でスキーマを出力するのが手間の少ない構成です。バックエンドがGoやPythonなど別の言語なら、JSON Schema(またはOpenAPI)を正本にし、各言語の型をそこから生成します。両方を手で書いて同期させる構成は、項目を1つ足すたびに2か所を直す必要があり、レビューで差分を突き合わせる手間も残るので避けます。複数のシステムをAPIでつなぐ際の型の受け渡しから設計したい場合は、API開発・システム連携の支援もご相談いただけます。
よくある質問:JSONの型定義とデータ型
JSON型とは何ですか?
2つの意味で使われます。1つはJSONの値として使えるデータ型(文字列・数値・真偽値・null・オブジェクト・配列の6つ)。もう1つは、MySQLやPostgreSQLでJSON文書を格納するカラムの型(MySQLのJSON型、PostgreSQLのjson型・jsonb型)です。
JSONで日付はどう扱いますか?
JSONに日付型は無いため、"2026-10-21T00:00:00.000Z"のようなISO 8601形式の文字列で持つのが一般的です。JSON.stringify()はDateをUTCのこの形式に変えますが、JSON.parse()は文字列のまま返すので、Dateに戻すにはreviverか受け取り後の変換が必要です。
JSONの数値に整数型はありますか?
ありません。1と1.0はJSONでは同じnumberです。整数であることを保証したい場合は、JSON Schemaの"type": "integer"やZodのz.number().int()で受け取り側が検証します。
JSONとXML・CSVでは型の扱いはどう違いますか?
XMLとCSVは値をすべて文字列として持ち、数値か真偽値かはスキーマや読み込む側の解釈で決まります。JSONは12800と"12800"、trueと"true"を書き方で区別できる点が違います。
JSONの読み方と拡張子は?
「ジェイソン」と読みます。JavaScript Object Notationの略で、ファイルの拡張子は.json、HTTPのメディアタイプはapplication/jsonです。フォーム送信のapplication/x-www-form-urlencodedとの違いはapplication/x-www-form-urlencodedとは?application/jsonとの違いと使い分けを参照してください。