JSON (Native)

シリアライゼーションJavaScriptWeb標準データ交換RESTful API軽量

ライブラリ

JSON (Native)

概要

JSON (JavaScript Object Notation)は、JavaScript標準のシリアライゼーション機能として提供される軽量なデータ交換形式です。JSON.stringify()とJSON.parse()による簡潔なAPIで、JavaScriptオブジェクトをJSON文字列に変換し、その逆変換を行います。すべてのJavaScript環境で標準で利用可能で、外部ライブラリを必要とせず、WebアプリケーションとAPIのデータ交換において最も広く使用されています。

詳細

JSON NativeはECMAScript 5仕様として標準化され、現在では全てのモダンブラウザとNode.jsで標準サポートされています。可読性の高いテキスト形式で、HTTP通信やファイル保存、設定ファイルなど様々な用途で活用されます。シンプルなAPIながら、replacer関数やspace引数による柔軟な出力制御、reviver関数による解析時のカスタマイズが可能です。

主な特徴

  • 標準サポート: 全てのJavaScript環境で追加ライブラリ不要
  • 軽量性: ゼロオーバーヘッドで高速動作
  • 可読性: 人間が読みやすいテキスト形式
  • 互換性: 多言語・多プラットフォーム間での高い互換性
  • 柔軟性: replacer/reviver関数による変換処理のカスタマイズ
  • Web標準: RESTful APIとWebアプリケーションのデファクトスタンダード

メリット・デメリット

メリット

  • JavaScript環境で外部ライブラリが不要(バンドルサイズ0KB)
  • 学習コストが低く、直感的なAPI設計
  • 多言語・多プラットフォームでの優れた互換性
  • 人間が読み書きしやすいテキスト形式
  • HTTPプロトコルとの親和性が高い
  • デバッグとログ出力が容易

デメリット

  • バイナリデータの効率的な表現が不可
  • 循環参照オブジェクトの処理不可
  • 日付型や関数などJavaScript固有型の標準サポートなし
  • バイナリ形式と比較してファイルサイズが大きい
  • セキュリティ面でJSONインジェクション攻撃のリスク
  • 大容量データでのパフォーマンス制約

参考ページ

書き方の例

基本的なシリアライゼーション

// 基本的なオブジェクトの変換
const userObject = {
  id: 123,
  name: '田中太郎',
  email: '[email protected]',
  isActive: true,
  lastLogin: null
};

// オブジェクトをJSON文字列に変換
const jsonString = JSON.stringify(userObject);
console.log(jsonString);
// 出力: {"id":123,"name":"田中太郎","email":"[email protected]","isActive":true,"lastLogin":null}

// JSON文字列をオブジェクトに変換
const parsedObject = JSON.parse(jsonString);
console.log(parsedObject);
// 出力: { id: 123, name: '田中太郎', email: '[email protected]', isActive: true, lastLogin: null }

// 配列の変換
const numbersArray = [1, 2, 3, 4, 5];
const arrayJson = JSON.stringify(numbersArray);
console.log(arrayJson); // 出力: "[1,2,3,4,5]"

const parsedArray = JSON.parse(arrayJson);
console.log(parsedArray); // 出力: [1, 2, 3, 4, 5]

高度な設定とカスタマイズ

// replacer関数を使用した出力制御
const sensitiveData = {
  username: 'user123',
  password: 'secret123',
  email: '[email protected]',
  apiKey: 'api_key_secret',
  publicInfo: 'this is public'
};

// 機密情報を除外するreplacer関数
function secureReplacer(key, value) {
  // パスワードやAPIキーを除外
  if (key === 'password' || key === 'apiKey') {
    return undefined;
  }
  return value;
}

const secureJson = JSON.stringify(sensitiveData, secureReplacer);
console.log(secureJson);
// 出力: {"username":"user123","email":"[email protected]","publicInfo":"this is public"}

// 特定のプロパティのみを含める
const allowedKeys = ['username', 'email'];
const filteredJson = JSON.stringify(sensitiveData, allowedKeys);
console.log(filteredJson);
// 出力: {"username":"user123","email":"[email protected]"}

// 整形された出力(space引数)
const prettyJson = JSON.stringify(userObject, null, 2);
console.log(prettyJson);
/*
{
  "id": 123,
  "name": "田中太郎",
  "email": "[email protected]",
  "isActive": true,
  "lastLogin": null
}
*/

// reviver関数を使用した解析時の変換
const dateJson = '{"name":"太郎","created":"2025-01-01T00:00:00.000Z","count":"42"}';

function dateReviver(key, value) {
  // ISO日付文字列をDateオブジェクトに変換
  if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
    return new Date(value);
  }
  // 数値文字列を数値に変換
  if (key === 'count' && typeof value === 'string' && !isNaN(value)) {
    return parseInt(value, 10);
  }
  return value;
}

const revivedObject = JSON.parse(dateJson, dateReviver);
console.log(revivedObject);
// 出力: { name: '太郎', created: 2025-01-01T00:00:00.000Z, count: 42 }
console.log(revivedObject.created instanceof Date); // true

パフォーマンス最適化

// 大容量データの効率的な処理
function createLargeData(size) {
  const data = [];
  for (let i = 0; i < size; i++) {
    data.push({
      id: i,
      name: `user_${i}`,
      value: Math.random(),
      timestamp: Date.now()
    });
  }
  return data;
}

// パフォーマンス測定関数
function measurePerformance(label, fn) {
  const start = performance.now();
  const result = fn();
  const end = performance.now();
  console.log(`${label}: ${(end - start).toFixed(2)}ms`);
  return result;
}

// 大量データの処理測定
const largeData = createLargeData(10000);

const jsonString = measurePerformance('Stringify 10,000 objects', () => {
  return JSON.stringify(largeData);
});

const parsedData = measurePerformance('Parse large JSON', () => {
  return JSON.parse(jsonString);
});

console.log(`JSON size: ${(jsonString.length / 1024).toFixed(2)} KB`);

// チャンク処理による大容量データの分割処理
function processInChunks(data, 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));
  }
  return chunks;
}

const chunks = measurePerformance('Chunk processing', () => {
  return processInChunks(largeData);
});

console.log(`Processed ${chunks.length} chunks`);

型安全性とバリデーション

// JSON スキーマ風のバリデーション
function validateUserObject(obj) {
  const requiredFields = ['id', 'name', 'email'];
  const errors = [];

  // 必須フィールドの検証
  for (const field of requiredFields) {
    if (!(field in obj)) {
      errors.push(`Missing required field: ${field}`);
    }
  }

  // 型検証
  if (typeof obj.id !== 'number') {
    errors.push('id must be a number');
  }

  if (typeof obj.name !== 'string') {
    errors.push('name must be a string');
  }

  if (typeof obj.email !== 'string' || !obj.email.includes('@')) {
    errors.push('email must be a valid email string');
  }

  return {
    isValid: errors.length === 0,
    errors
  };
}

// 安全なJSON解析関数
function safeJsonParse(jsonString, validator = null) {
  try {
    const parsed = JSON.parse(jsonString);
    
    if (validator) {
      const validation = validator(parsed);
      if (!validation.isValid) {
        throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
      }
    }
    
    return { success: true, data: parsed };
  } catch (error) {
    return { success: false, error: error.message };
  }
}

// 使用例
const validJson = '{"id":123,"name":"田中太郎","email":"[email protected]"}';
const invalidJson = '{"name":"田中太郎","email":"invalid-email"}';

const validResult = safeJsonParse(validJson, validateUserObject);
console.log('Valid result:', validResult);

const invalidResult = safeJsonParse(invalidJson, validateUserObject);
console.log('Invalid result:', invalidResult);

// TypeScript風の型チェック(実行時)
function isUserType(obj) {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    typeof obj.id === 'number' &&
    typeof obj.name === 'string' &&
    typeof obj.email === 'string'
  );
}

function parseUserJson(jsonString) {
  const result = safeJsonParse(jsonString);
  if (result.success && isUserType(result.data)) {
    return result.data;
  }
  throw new Error('Invalid user JSON format');
}

エラーハンドリング

// 堅牢なJSONパース処理
function robustJsonParse(input, options = {}) {
  const {
    fallback = null,
    logErrors = false,
    attemptRepair = false
  } = options;

  // 入力値検証
  if (typeof input !== 'string') {
    if (logErrors) console.error('Input is not a string:', typeof input);
    return fallback;
  }

  if (input.trim() === '') {
    if (logErrors) console.error('Input is empty string');
    return fallback;
  }

  try {
    return JSON.parse(input);
  } catch (error) {
    if (logErrors) {
      console.error('JSON parse error:', {
        message: error.message,
        input: input.substring(0, 100) + (input.length > 100 ? '...' : ''),
        position: error.message.match(/position (\d+)/)?.[1]
      });
    }

    // 自動修復の試行
    if (attemptRepair) {
      try {
        // 末尾カンマの除去
        let repairedInput = input.replace(/,(\s*[}\]])/g, '$1');
        
        // 不正なクォートの修正
        repairedInput = repairedInput.replace(/'/g, '"');
        
        const repaired = JSON.parse(repairedInput);
        if (logErrors) console.log('JSON repair successful');
        return repaired;
      } catch (repairError) {
        if (logErrors) console.error('JSON repair failed:', repairError.message);
      }
    }

    return fallback;
  }
}

// 使用例
const malformedJson = `{
  "name": "テスト",
  "items": [1, 2, 3,],
  "active": true,
}`;

const result = robustJsonParse(malformedJson, {
  fallback: {},
  logErrors: true,
  attemptRepair: true
});

console.log('Parsed result:', result);

// ストリーミングJSONパーサー(大容量ファイル対応)
function parseJsonStream(jsonString, onObject, onError) {
  let depth = 0;
  let currentObject = '';
  let inString = false;
  let escaped = false;

  for (let i = 0; i < jsonString.length; i++) {
    const char = jsonString[i];
    currentObject += char;

    if (!inString) {
      if (char === '{' || char === '[') {
        depth++;
      } else if (char === '}' || char === ']') {
        depth--;
        
        if (depth === 0) {
          // 完全なオブジェクトを発見
          try {
            const obj = JSON.parse(currentObject);
            onObject(obj);
            currentObject = '';
          } catch (error) {
            onError(error, currentObject);
          }
        }
      }
    }

    if (char === '"' && !escaped) {
      inString = !inString;
    }
    escaped = char === '\\' && !escaped;
  }
}

// ストリーミングパーサーの使用例
const streamData = '{"id":1,"name":"A"}{"id":2,"name":"B"}{"id":3,"name":"C"}';

const objects = [];
parseJsonStream(streamData, 
  (obj) => objects.push(obj),
  (error, content) => console.error('Parse error:', error.message, content)
);

console.log('Streamed objects:', objects);

Web API統合

// Fetch APIとJSONの統合
class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      headers: { ...this.defaultHeaders, ...options.headers },
      ...options
    };

    // リクエストボディのJSONシリアライゼーション
    if (config.body && typeof config.body === 'object') {
      config.body = JSON.stringify(config.body);
    }

    try {
      const response = await fetch(url, config);
      
      // レスポンスヘッダーの確認
      const contentType = response.headers.get('content-type');
      if (!contentType || !contentType.includes('application/json')) {
        throw new Error(`Unexpected content type: ${contentType}`);
      }

      const data = await response.json();

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${data.message || 'Request failed'}`);
      }

      return data;
    } catch (error) {
      console.error('API request failed:', error);
      throw error;
    }
  }

  async get(endpoint) {
    return this.request(endpoint, { method: 'GET' });
  }

  async post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: data
    });
  }

  async put(endpoint, data) {
    return this.request(endpoint, {
      method: 'PUT',
      body: data
    });
  }

  async delete(endpoint) {
    return this.request(endpoint, { method: 'DELETE' });
  }
}

// APIクライアントの使用例
const api = new ApiClient('https://api.example.com');

async function manageUsers() {
  try {
    // ユーザー一覧取得
    const users = await api.get('/users');
    console.log('Users:', users);

    // 新規ユーザー作成
    const newUser = {
      name: '佐藤花子',
      email: '[email protected]',
      department: '開発部'
    };

    const createdUser = await api.post('/users', newUser);
    console.log('Created user:', createdUser);

    // ユーザー情報更新
    const updatedUser = await api.put(`/users/${createdUser.id}`, {
      ...createdUser,
      department: '企画部'
    });
    console.log('Updated user:', updatedUser);

  } catch (error) {
    console.error('User management failed:', error);
  }
}

// WebSocketとJSONの統合
class JsonWebSocketClient {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.messageHandlers = new Map();
  }

  connect() {
    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(this.url);
      
      this.ws.onopen = () => {
        console.log('WebSocket connected');
        resolve();
      };

      this.ws.onerror = (error) => {
        console.error('WebSocket error:', error);
        reject(error);
      };

      this.ws.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data);
          this.handleMessage(data);
        } catch (error) {
          console.error('Failed to parse WebSocket message:', error);
        }
      };
    });
  }

  send(type, payload) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      const message = {
        type,
        payload,
        timestamp: Date.now()
      };
      this.ws.send(JSON.stringify(message));
    }
  }

  on(messageType, handler) {
    this.messageHandlers.set(messageType, handler);
  }

  handleMessage(data) {
    const handler = this.messageHandlers.get(data.type);
    if (handler) {
      handler(data.payload);
    }
  }

  disconnect() {
    if (this.ws) {
      this.ws.close();
    }
  }
}

// WebSocketクライアントの使用例
const wsClient = new JsonWebSocketClient('wss://api.example.com/ws');

wsClient.on('user_update', (userData) => {
  console.log('User updated:', userData);
});

wsClient.on('notification', (notification) => {
  console.log('Notification:', notification);
});

// 接続とメッセージ送信
wsClient.connect().then(() => {
  wsClient.send('subscribe', { channel: 'user_updates' });
  wsClient.send('user_action', { action: 'get_profile', userId: 123 });
});