JSON (Native)
シリアライゼーションライブラリ
JSON (Native)
概要
JSON(JavaScript Object Notation)は、TypeScriptおよびJavaScriptの標準的なデータ交換フォーマットです。ネイティブなJSON.stringify()とJSON.parse()メソッドを使用して、オブジェクトとJSON文字列の間で変換を行います。軽量で人間が読みやすく、Web開発において最も広く使用されているシリアライゼーションフォーマットです。
詳細
JSONは「JavaScript Object Notation」の略で、JavaScriptのオブジェクトリテラル記法に基づいたテキストベースのデータ交換フォーマットです。TypeScriptでは、標準のJSON APIを使用してオブジェクトの永続化、API通信、設定ファイルの管理などを行います。
主な特徴:
- ネイティブサポート: ブラウザとNode.jsで標準提供
- 人間が読める: テキストベースで視覚的に理解しやすい
- 軽量: 最小限のオーバーヘッドでデータ表現
- 言語非依存: 多くのプログラミング言語でサポート
- Web標準: REST API、設定ファイル、データ交換で広く使用
技術的詳細:
- JSON.stringify(): オブジェクトをJSON文字列に変換
- JSON.parse(): JSON文字列をオブジェクトに変換
- replacer関数: シリアライゼーション時のカスタマイズ
- reviver関数: デシリアライゼーション時のカスタマイズ
- スペース引数: 整形された出力の生成
対応データ型:
- プリミティブ型(string、number、boolean、null)
- オブジェクト(Object)
- 配列(Array)
- 制限事項:undefined、function、symbol、Date(文字列として扱われる)
メリット・デメリット
メリット
- 標準ライブラリのため追加の依存関係不要
- 人間が読みやすく、デバッグが容易
- 軽量でパフォーマンスが良い
- 多くのプログラミング言語でサポート
- Web API、設定ファイル、データ交換で広く使用
- TypeScriptの型システムと良好に統合
デメリット
- Date、undefined、function、BigIntなどの型をサポートしない
- 循環参照のあるオブジェクトを扱えない
- バイナリデータの直接サポートなし
- 大きなデータセットではパフォーマンスが劣る場合がある
- 精度の高い数値計算には適さない場合がある
参考ページ
- MDN Web Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
- JSON公式サイト: https://www.json.org/
- TypeScript Handbook: https://www.typescriptlang.org/docs/
- RFC 7159: https://tools.ietf.org/html/rfc7159
書き方の例
基本的な使用方法
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
const user: User = {
id: 1,
name: 'Alice',
email: '[email protected]',
active: true
};
// オブジェクトをJSON文字列に変換
const jsonString = JSON.stringify(user);
console.log('JSON:', jsonString);
// JSON文字列をオブジェクトに変換
const parsedUser: User = JSON.parse(jsonString);
console.log('Parsed:', parsedUser);
整形された出力
interface Config {
server: {
host: string;
port: number;
ssl: boolean;
};
database: {
host: string;
port: number;
name: string;
};
features: string[];
}
const config: Config = {
server: {
host: 'localhost',
port: 8080,
ssl: false
},
database: {
host: 'localhost',
port: 5432,
name: 'myapp'
},
features: ['auth', 'logging', 'metrics']
};
// 整形されたJSON出力
const prettyJson = JSON.stringify(config, null, 2);
console.log('Pretty JSON:\n', prettyJson);
replacer関数によるカスタマイズ
interface SensitiveData {
username: string;
password: string;
apiKey: string;
email: string;
}
const sensitiveData: SensitiveData = {
username: 'alice',
password: 'secret123',
apiKey: 'sk-1234567890abcdef',
email: '[email protected]'
};
// 機密データを除外するreplacer関数
const safeReplacer = (key: string, value: any): any => {
if (key === 'password' || key === 'apiKey') {
return '[REDACTED]';
}
return value;
};
const safeJson = JSON.stringify(sensitiveData, safeReplacer, 2);
console.log('Safe JSON:\n', safeJson);
reviver関数によるカスタマイズ
interface DateDocument {
id: number;
title: string;
createdAt: Date;
updatedAt: Date;
}
const doc: DateDocument = {
id: 1,
title: 'My Document',
createdAt: new Date('2023-01-01'),
updatedAt: new Date('2023-01-15')
};
// Date型をISO文字列でシリアライズ
const jsonString = JSON.stringify(doc);
console.log('Serialized:', jsonString);
// reviver関数でDate型を復元
const dateReviver = (key: string, value: any): any => {
if (key.includes('At') && typeof value === 'string') {
return new Date(value);
}
return value;
};
const parsedDoc: DateDocument = JSON.parse(jsonString, dateReviver);
console.log('Parsed with dates:', parsedDoc);
console.log('createdAt is Date?', parsedDoc.createdAt instanceof Date);
配列の処理
interface Product {
id: number;
name: string;
price: number;
tags: string[];
}
const products: Product[] = [
{
id: 1,
name: 'Laptop',
price: 999.99,
tags: ['electronics', 'computers']
},
{
id: 2,
name: 'Mouse',
price: 29.99,
tags: ['electronics', 'accessories']
}
];
// 配列をJSON文字列に変換
const jsonArray = JSON.stringify(products);
console.log('JSON Array:', jsonArray);
// JSON文字列を配列に変換
const parsedProducts: Product[] = JSON.parse(jsonArray);
console.log('Parsed Products:', parsedProducts.length, 'items');
// 特定の条件でフィルタリング
const electronicProducts = parsedProducts.filter(p =>
p.tags.includes('electronics')
);
console.log('Electronic products:', electronicProducts.length);
エラーハンドリング
function safeJsonParse<T>(jsonString: string): T | null {
try {
return JSON.parse(jsonString) as T;
} catch (error) {
console.error('JSON Parse Error:', error);
return null;
}
}
function safeJsonStringify<T>(obj: T): string | null {
try {
return JSON.stringify(obj);
} catch (error) {
console.error('JSON Stringify Error:', error);
return null;
}
}
// 使用例
const invalidJson = '{"name": "Alice", "age": 30,}'; // 末尾のカンマが無効
const result = safeJsonParse<User>(invalidJson);
if (result) {
console.log('Valid JSON:', result);
} else {
console.log('Invalid JSON provided');
}
深い階層のオブジェクト
interface NestedData {
level1: {
level2: {
level3: {
value: string;
metadata: {
created: string;
author: string;
};
};
};
};
}
const nestedData: NestedData = {
level1: {
level2: {
level3: {
value: 'deep value',
metadata: {
created: '2023-01-01',
author: 'Alice'
}
}
}
}
};
// 深い階層もそのまま処理
const jsonString = JSON.stringify(nestedData, null, 2);
console.log('Nested JSON:\n', jsonString);
const parsed: NestedData = JSON.parse(jsonString);
console.log('Deep value:', parsed.level1.level2.level3.value);
JSONスキーマ検証(手動)
interface ApiResponse {
success: boolean;
data?: any;
error?: string;
}
function validateApiResponse(json: string): ApiResponse | null {
try {
const parsed = JSON.parse(json);
// 必須フィールドの検証
if (typeof parsed.success !== 'boolean') {
throw new Error('success field must be boolean');
}
// 条件付きフィールドの検証
if (parsed.success && !parsed.data) {
throw new Error('data field required when success is true');
}
if (!parsed.success && !parsed.error) {
throw new Error('error field required when success is false');
}
return parsed as ApiResponse;
} catch (error) {
console.error('Validation error:', error);
return null;
}
}
// テスト
const validResponse = '{"success": true, "data": {"id": 1, "name": "Alice"}}';
const invalidResponse = '{"success": true}'; // dataが欠けている
console.log('Valid:', validateApiResponse(validResponse));
console.log('Invalid:', validateApiResponse(invalidResponse));
大きなデータセットの処理
interface LogEntry {
timestamp: string;
level: string;
message: string;
metadata?: Record<string, any>;
}
function processLargeDataset(entries: LogEntry[]): void {
const start = Date.now();
// 大きなデータセットをJSON文字列に変換
const jsonString = JSON.stringify(entries);
const serializeTime = Date.now() - start;
console.log(`Serialized ${entries.length} entries in ${serializeTime}ms`);
console.log(`JSON size: ${jsonString.length} characters`);
// JSON文字列を配列に変換
const parseStart = Date.now();
const parsed: LogEntry[] = JSON.parse(jsonString);
const parseTime = Date.now() - parseStart;
console.log(`Parsed ${parsed.length} entries in ${parseTime}ms`);
}
// 大きなデータセットを生成
const largeDataset: LogEntry[] = Array.from({ length: 10000 }, (_, i) => ({
timestamp: new Date(Date.now() - i * 1000).toISOString(),
level: i % 4 === 0 ? 'ERROR' : 'INFO',
message: `Log message ${i}`,
metadata: { requestId: `req-${i}` }
}));
processLargeDataset(largeDataset);
TypeScriptの型との統合
// 型ガードを使用した安全なJSONパース
function isUser(obj: any): obj is User {
return obj &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string' &&
typeof obj.active === 'boolean';
}
function parseUserJson(jsonString: string): User | null {
try {
const parsed = JSON.parse(jsonString);
if (isUser(parsed)) {
return parsed;
} else {
console.error('Invalid user data structure');
return null;
}
} catch (error) {
console.error('JSON parsing failed:', error);
return null;
}
}
// 使用例
const userJson = '{"id": 1, "name": "Alice", "email": "[email protected]", "active": true}';
const user = parseUserJson(userJson);
if (user) {
console.log(`User ${user.name} (${user.email}) is ${user.active ? 'active' : 'inactive'}`);
}
JSONファイルの読み書き(Node.js)
import { promises as fs } from 'fs';
import { join } from 'path';
interface AppConfig {
version: string;
environment: string;
features: {
[key: string]: boolean;
};
}
async function saveConfig(config: AppConfig, filename: string): Promise<void> {
try {
const jsonString = JSON.stringify(config, null, 2);
await fs.writeFile(filename, jsonString, 'utf8');
console.log(`Config saved to ${filename}`);
} catch (error) {
console.error('Error saving config:', error);
}
}
async function loadConfig(filename: string): Promise<AppConfig | null> {
try {
const jsonString = await fs.readFile(filename, 'utf8');
return JSON.parse(jsonString) as AppConfig;
} catch (error) {
console.error('Error loading config:', error);
return null;
}
}
// 使用例
const config: AppConfig = {
version: '1.0.0',
environment: 'production',
features: {
auth: true,
logging: true,
metrics: false
}
};
const configPath = join(__dirname, 'config.json');
// 設定を保存
await saveConfig(config, configPath);
// 設定を読み込み
const loadedConfig = await loadConfig(configPath);
if (loadedConfig) {
console.log('Loaded config:', loadedConfig);
}