JSON (JavaScript Object Notation)

JavaScript標準のデータ交換形式とシリアライゼーション機能

serializationjsonjavascriptstandardtextweb

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))  // 固定小数点
};

他のシリアライゼーション形式との比較

特徴JSONXMLYAMLMessagePackProtocol Buffers
可読性
パフォーマンス
サイズ
標準サポート優秀良好制限的制限的制限的
スキーマ無しDTD/XSD無し無し必須
コメント無し有り有り無し有り

まとめ

JSON は Web 開発において最も広く使用されているデータ交換形式です。以下の特徴を持ちます:

利点

  • 標準サポート: 全ての JavaScript エンジンで利用可能
  • 可読性: 人間が読みやすい形式
  • 互換性: 全てのプログラミング言語で対応
  • シンプル: 学習コストが低い

制限事項

  • データ型の制限: 限定的なデータ型のサポート
  • パフォーマンス: バイナリ形式と比較して劣る
  • サイズ: 冗長性によるファイルサイズの増加

JSON は、可読性と互換性が重要な Web API、設定ファイル、データ交換において最適な選択肢です。パフォーマンスが重要な場合は、MessagePack や Protocol Buffers の併用を検討してください。