JSON (JavaScript Object Notation)
JavaScript標準のデータ交換形式とシリアライゼーション機能
JSON (JavaScript Object Notation)
JSON は、JavaScript のネイティブなデータ交換形式であり、人間が読みやすく機械が解析しやすい軽量なテキストベースのデータ形式です。現在、Web開発における標準的なデータ形式として広く使用されています。
主な特徴
- 標準サポート: JavaScript エンジンに組み込まれているため、追加ライブラリ不要
- 人間が読める: テキスト形式で可読性が高い
- 軽量: シンプルな構文で効率的
- 広範囲サポート: すべてのプログラミング言語で対応
- Web標準: HTTP APIやWebサービスで標準的に使用
- デバッグが容易: テキスト形式のため問題の特定が簡単
主なユースケース
- Web API: RESTful APIでのデータ交換
- 設定ファイル: アプリケーション設定の保存
- データベース: NoSQLデータベースでのドキュメント保存
- ローカルストレージ: ブラウザでのデータ保存
- ログ出力: 構造化ログの記録
- データ移行: システム間でのデータ転送
基本的な使い方
1. 基本的なシリアライゼーション
// オブジェクトをJSONに変換
const user = {
id: 1,
name: "田中太郎",
email: "[email protected]",
age: 30,
active: true,
preferences: {
theme: "dark",
notifications: true
},
tags: ["premium", "verified"]
};
// JSON文字列にシリアライズ
const jsonString = JSON.stringify(user);
console.log(jsonString);
// {"id":1,"name":"田中太郎","email":"[email protected]","age":30,"active":true,"preferences":{"theme":"dark","notifications":true},"tags":["premium","verified"]}
// JSON文字列からオブジェクトに復元
const parsedUser = JSON.parse(jsonString);
console.log(parsedUser.name); // "田中太郎"
2. 整形されたJSON出力
// 読みやすい形式でJSON出力
const formattedJson = JSON.stringify(user, null, 2);
console.log(formattedJson);
/*
{
"id": 1,
"name": "田中太郎",
"email": "[email protected]",
"age": 30,
"active": true,
"preferences": {
"theme": "dark",
"notifications": true
},
"tags": [
"premium",
"verified"
]
}
*/
3. 選択的なシリアライゼーション
// 特定のプロパティのみシリアライズ
const user = {
id: 1,
name: "田中太郎",
email: "[email protected]",
password: "secret123",
age: 30
};
// passwordフィールドを除外してシリアライズ
const safeJson = JSON.stringify(user, ['id', 'name', 'email', 'age']);
console.log(safeJson);
// {"id":1,"name":"田中太郎","email":"[email protected]","age":30}
高度な使用例
1. カスタムシリアライゼーション
// replacer関数を使用したカスタムシリアライゼーション
const data = {
user: "田中太郎",
password: "secret123",
balance: 1000.50,
createdAt: new Date(),
metadata: {
version: "1.0.0",
debug: true
}
};
const customJson = JSON.stringify(data, (key, value) => {
// パスワードフィールドを除外
if (key === 'password') {
return undefined;
}
// Date オブジェクトを ISO 文字列に変換
if (value instanceof Date) {
return value.toISOString();
}
// デバッグ情報を除外
if (key === 'debug') {
return undefined;
}
return value;
});
console.log(customJson);
// {"user":"田中太郎","balance":1000.5,"createdAt":"2024-01-01T00:00:00.000Z","metadata":{"version":"1.0.0"}}
2. カスタムデシリアライゼーション
// reviver関数を使用したカスタムデシリアライゼーション
const jsonString = '{"user":"田中太郎","balance":1000.5,"createdAt":"2024-01-01T00:00:00.000Z","tags":["premium","verified"]}';
const parsedData = JSON.parse(jsonString, (key, value) => {
// ISO日付文字列をDateオブジェクトに変換
if (key === 'createdAt' && typeof value === 'string') {
return new Date(value);
}
// 数値の精度を調整
if (key === 'balance' && typeof value === 'number') {
return Math.round(value * 100) / 100;
}
return value;
});
console.log(parsedData.createdAt instanceof Date); // true
console.log(parsedData.balance); // 1000.5
3. 深いオブジェクトのコピー
// 深いコピーの実現
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
const original = {
name: "田中太郎",
address: {
city: "東京",
country: "日本"
},
hobbies: ["読書", "映画鑑賞"]
};
const cloned = deepClone(original);
cloned.address.city = "大阪";
console.log(original.address.city); // "東京" (元のオブジェクトは変更されない)
console.log(cloned.address.city); // "大阪"
実用的な例
1. API通信での使用
// RESTful APIでのデータ送信
async function createUser(userData) {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error('ユーザー作成エラー:', error);
throw error;
}
}
// 使用例
const newUser = {
name: "田中太郎",
email: "[email protected]",
age: 30
};
createUser(newUser).then(user => {
console.log('作成されたユーザー:', user);
});
2. ローカルストレージでの使用
// ローカルストレージへの保存
class UserPreferences {
constructor() {
this.storageKey = 'user-preferences';
}
save(preferences) {
try {
const jsonString = JSON.stringify(preferences);
localStorage.setItem(this.storageKey, jsonString);
console.log('設定を保存しました');
} catch (error) {
console.error('設定保存エラー:', error);
}
}
load() {
try {
const jsonString = localStorage.getItem(this.storageKey);
if (jsonString) {
return JSON.parse(jsonString);
}
return null;
} catch (error) {
console.error('設定読み込みエラー:', error);
return null;
}
}
clear() {
localStorage.removeItem(this.storageKey);
console.log('設定をクリアしました');
}
}
// 使用例
const preferences = new UserPreferences();
// 設定の保存
preferences.save({
theme: 'dark',
language: 'ja',
notifications: true,
autoSave: false
});
// 設定の読み込み
const userPrefs = preferences.load();
console.log(userPrefs?.theme); // 'dark'
3. 設定ファイルの処理
// 設定ファイルの読み込み (Node.js)
const fs = require('fs');
class ConfigManager {
constructor(configPath) {
this.configPath = configPath;
this.config = {};
this.load();
}
load() {
try {
const configData = fs.readFileSync(this.configPath, 'utf8');
this.config = JSON.parse(configData);
console.log('設定ファイルを読み込みました');
} catch (error) {
console.error('設定ファイル読み込みエラー:', error);
this.config = this.getDefaultConfig();
}
}
save() {
try {
const configData = JSON.stringify(this.config, null, 2);
fs.writeFileSync(this.configPath, configData, 'utf8');
console.log('設定ファイルを保存しました');
} catch (error) {
console.error('設定ファイル保存エラー:', error);
}
}
get(key) {
return this.config[key];
}
set(key, value) {
this.config[key] = value;
}
getDefaultConfig() {
return {
port: 3000,
environment: 'development',
database: {
host: 'localhost',
port: 5432,
name: 'myapp'
},
logging: {
level: 'info',
file: 'app.log'
}
};
}
}
// 使用例
const config = new ConfigManager('./config.json');
console.log('ポート:', config.get('port'));
config.set('port', 8080);
config.save();
パフォーマンスとベストプラクティス
1. 大きなオブジェクトの処理
// 大きなオブジェクトを効率的に処理
function processLargeData(data) {
const startTime = performance.now();
try {
// チャンクサイズを指定して処理
const chunkSize = 1000;
const chunks = [];
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
chunks.push(JSON.stringify(chunk));
}
const endTime = performance.now();
console.log(`処理時間: ${endTime - startTime}ms`);
return chunks;
} catch (error) {
console.error('処理エラー:', error);
return [];
}
}
2. ストリーミング処理
// ストリーミングJSONの処理
class JsonStreamProcessor {
constructor() {
this.buffer = '';
this.depth = 0;
}
processChunk(chunk) {
this.buffer += chunk;
const objects = [];
let startIndex = 0;
for (let i = 0; i < this.buffer.length; i++) {
const char = this.buffer[i];
if (char === '{') {
this.depth++;
} else if (char === '}') {
this.depth--;
if (this.depth === 0) {
// 完全なオブジェクトを検出
const objectStr = this.buffer.substring(startIndex, i + 1);
try {
const obj = JSON.parse(objectStr);
objects.push(obj);
} catch (error) {
console.warn('JSON解析エラー:', error);
}
startIndex = i + 1;
}
}
}
// 未処理のデータを保持
this.buffer = this.buffer.substring(startIndex);
return objects;
}
}
3. エラー処理とバリデーション
// 堅牢なJSON処理
class JsonValidator {
static isValidJson(str) {
try {
JSON.parse(str);
return true;
} catch {
return false;
}
}
static safeStringify(obj, defaultValue = '{}') {
try {
return JSON.stringify(obj);
} catch (error) {
console.warn('JSON stringify エラー:', error);
return defaultValue;
}
}
static safeParse(str, defaultValue = {}) {
try {
return JSON.parse(str);
} catch (error) {
console.warn('JSON parse エラー:', error);
return defaultValue;
}
}
static validateSchema(obj, schema) {
for (const key in schema) {
if (schema.hasOwnProperty(key)) {
const expectedType = schema[key];
const actualType = typeof obj[key];
if (actualType !== expectedType) {
throw new Error(`${key}: 期待される型 ${expectedType}, 実際の型 ${actualType}`);
}
}
}
return true;
}
}
// 使用例
const userSchema = {
name: 'string',
age: 'number',
active: 'boolean'
};
const userData = '{"name":"田中太郎","age":30,"active":true}';
if (JsonValidator.isValidJson(userData)) {
const user = JsonValidator.safeParse(userData);
try {
JsonValidator.validateSchema(user, userSchema);
console.log('バリデーション成功:', user);
} catch (error) {
console.error('スキーマバリデーションエラー:', error);
}
}
制限事項と注意点
1. サポートされていないデータ型
// JSONでサポートされていないデータ型
const problematicData = {
date: new Date(), // Dateオブジェクト → 文字列に変換される
func: function() { return 42; }, // 関数 → 除外される
undef: undefined, // undefined → 除外される
symbol: Symbol('test'), // Symbol → 除外される
bigint: 123n, // BigInt → エラーになる
circular: null
};
// 循環参照の問題
problematicData.circular = problematicData;
// 解決策
const safeData = {
date: new Date().toISOString(), // ISO文字列に変換
value: 42, // プリミティブ値を使用
defined: "defined value", // 定義された値を使用
id: "test-id", // 文字列IDを使用
number: 123 // 通常の数値を使用
};
console.log(JSON.stringify(safeData));
2. 精度の問題
// 数値精度の問題
const precisionTest = {
largeNumber: 9007199254740991, // Number.MAX_SAFE_INTEGER
decimal: 0.1 + 0.2, // 0.30000000000000004
precision: 1.23456789012345678901 // 精度が失われる
};
console.log(JSON.stringify(precisionTest));
// {"largeNumber":9007199254740991,"decimal":0.30000000000000004,"precision":1.2345678901234568}
// 解決策
const precisionSafe = {
largeNumber: "9007199254740991", // 文字列として保存
decimal: Math.round((0.1 + 0.2) * 100) / 100, // 適切な丸め
precision: Number(1.23456789012345678901.toFixed(10)) // 固定小数点
};
他のシリアライゼーション形式との比較
| 特徴 | JSON | XML | YAML | MessagePack | Protocol Buffers |
|---|---|---|---|---|---|
| 可読性 | 高 | 中 | 高 | 低 | 低 |
| パフォーマンス | 中 | 低 | 低 | 高 | 高 |
| サイズ | 中 | 大 | 中 | 小 | 小 |
| 標準サポート | 優秀 | 良好 | 制限的 | 制限的 | 制限的 |
| スキーマ | 無し | DTD/XSD | 無し | 無し | 必須 |
| コメント | 無し | 有り | 有り | 無し | 有り |
まとめ
JSON は Web 開発において最も広く使用されているデータ交換形式です。以下の特徴を持ちます:
利点
- 標準サポート: 全ての JavaScript エンジンで利用可能
- 可読性: 人間が読みやすい形式
- 互換性: 全てのプログラミング言語で対応
- シンプル: 学習コストが低い
制限事項
- データ型の制限: 限定的なデータ型のサポート
- パフォーマンス: バイナリ形式と比較して劣る
- サイズ: 冗長性によるファイルサイズの増加
JSON は、可読性と互換性が重要な Web API、設定ファイル、データ交換において最適な選択肢です。パフォーマンスが重要な場合は、MessagePack や Protocol Buffers の併用を検討してください。